<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>【两万字详解】Nand Flash 必看知识 | ShuangChenYue</title>
    <meta name="generator" content="VuePress 1.9.10">
    <link rel="icon" href="https://cdn.jsdelivr.net/gh/cmty256/imgs-blog@main/logo/白云.38zbldnhh180.jpg">
    <meta name="description" content="满招损，谦受益">
    <meta name="keywords" content="专注于Cpp语言的旅行者">
    
    <link rel="preload" href="/assets/css/0.styles.952d6952.css" as="style"><link rel="preload" href="/assets/js/app.67adcfd9.js" as="script"><link rel="preload" href="/assets/js/4.9aaa1650.js" as="script"><link rel="preload" href="/assets/js/1.5474518c.js" as="script"><link rel="preload" href="/assets/js/3.593d14fc.js" as="script"><link rel="preload" href="/assets/js/190.c284595f.js" as="script"><link rel="prefetch" href="/assets/js/10.3242746b.js"><link rel="prefetch" href="/assets/js/100.9224de43.js"><link rel="prefetch" href="/assets/js/101.f0d1b059.js"><link rel="prefetch" href="/assets/js/102.996bfc6d.js"><link rel="prefetch" href="/assets/js/103.9bfdbd6f.js"><link rel="prefetch" href="/assets/js/104.8613f283.js"><link rel="prefetch" href="/assets/js/105.aa6e809e.js"><link rel="prefetch" href="/assets/js/106.90192392.js"><link rel="prefetch" href="/assets/js/107.e82a40b7.js"><link rel="prefetch" href="/assets/js/108.994cd438.js"><link rel="prefetch" href="/assets/js/109.ec15acc2.js"><link rel="prefetch" href="/assets/js/11.c04b41c1.js"><link rel="prefetch" href="/assets/js/110.c32d8576.js"><link rel="prefetch" href="/assets/js/111.453b5d50.js"><link rel="prefetch" href="/assets/js/112.ffbdb3a4.js"><link rel="prefetch" href="/assets/js/113.12b8ad7d.js"><link rel="prefetch" href="/assets/js/114.899d2998.js"><link rel="prefetch" href="/assets/js/115.b7ad9576.js"><link rel="prefetch" href="/assets/js/116.a8394748.js"><link rel="prefetch" href="/assets/js/117.0edfe25b.js"><link rel="prefetch" href="/assets/js/118.9161b1fe.js"><link rel="prefetch" href="/assets/js/119.be59e21b.js"><link rel="prefetch" href="/assets/js/12.41437bf6.js"><link rel="prefetch" href="/assets/js/120.bcf439fb.js"><link rel="prefetch" href="/assets/js/121.c3d251b8.js"><link rel="prefetch" href="/assets/js/122.62b1caba.js"><link rel="prefetch" href="/assets/js/123.787c2ab0.js"><link rel="prefetch" href="/assets/js/124.a880746f.js"><link rel="prefetch" href="/assets/js/125.d8edfe7b.js"><link rel="prefetch" href="/assets/js/126.4ff01546.js"><link rel="prefetch" href="/assets/js/127.9416d1ff.js"><link rel="prefetch" href="/assets/js/128.01a4a7a0.js"><link rel="prefetch" href="/assets/js/129.76876665.js"><link rel="prefetch" href="/assets/js/13.922328e9.js"><link rel="prefetch" href="/assets/js/130.7f631dd9.js"><link rel="prefetch" href="/assets/js/131.c9e0fde9.js"><link rel="prefetch" href="/assets/js/132.1c04cde5.js"><link rel="prefetch" href="/assets/js/133.e8f381cd.js"><link rel="prefetch" href="/assets/js/134.03d19f8b.js"><link rel="prefetch" href="/assets/js/135.44607494.js"><link rel="prefetch" href="/assets/js/136.6a1eb3c9.js"><link rel="prefetch" href="/assets/js/137.27898fd1.js"><link rel="prefetch" href="/assets/js/138.5bc0cf54.js"><link rel="prefetch" href="/assets/js/139.c2d1addc.js"><link rel="prefetch" href="/assets/js/14.e54d7526.js"><link rel="prefetch" href="/assets/js/140.052ec8e4.js"><link rel="prefetch" href="/assets/js/141.131abb5a.js"><link rel="prefetch" href="/assets/js/142.6ba6c07b.js"><link rel="prefetch" href="/assets/js/143.5dd51d22.js"><link rel="prefetch" href="/assets/js/144.b45afca8.js"><link rel="prefetch" href="/assets/js/145.faa9fb04.js"><link rel="prefetch" href="/assets/js/146.b54c024d.js"><link rel="prefetch" href="/assets/js/147.a1223242.js"><link rel="prefetch" href="/assets/js/148.4767bcb2.js"><link rel="prefetch" href="/assets/js/149.b65ab046.js"><link rel="prefetch" href="/assets/js/15.7082a3da.js"><link rel="prefetch" href="/assets/js/150.9bd8c175.js"><link rel="prefetch" href="/assets/js/151.9f830e96.js"><link rel="prefetch" href="/assets/js/152.41cde7f0.js"><link rel="prefetch" href="/assets/js/153.f57d65e0.js"><link rel="prefetch" href="/assets/js/154.5d7c8d51.js"><link rel="prefetch" href="/assets/js/155.0ae99532.js"><link rel="prefetch" href="/assets/js/156.5a54e043.js"><link rel="prefetch" href="/assets/js/157.c25b5d40.js"><link rel="prefetch" href="/assets/js/158.aa025b46.js"><link rel="prefetch" href="/assets/js/159.47939d88.js"><link rel="prefetch" href="/assets/js/16.fc775b7b.js"><link rel="prefetch" href="/assets/js/160.f8624459.js"><link rel="prefetch" href="/assets/js/161.7a075dc2.js"><link rel="prefetch" href="/assets/js/162.1d48f266.js"><link rel="prefetch" href="/assets/js/163.5d68a99f.js"><link rel="prefetch" href="/assets/js/164.1262d0e5.js"><link rel="prefetch" href="/assets/js/165.2ccf0bdd.js"><link rel="prefetch" href="/assets/js/166.21ece4d9.js"><link rel="prefetch" href="/assets/js/167.bf8adb95.js"><link rel="prefetch" href="/assets/js/168.1cb8440d.js"><link rel="prefetch" href="/assets/js/169.1dd1e396.js"><link rel="prefetch" href="/assets/js/17.ecc7be70.js"><link rel="prefetch" href="/assets/js/170.c29ec18f.js"><link rel="prefetch" href="/assets/js/171.38820827.js"><link rel="prefetch" href="/assets/js/172.bbc8ffc6.js"><link rel="prefetch" href="/assets/js/173.470e21e7.js"><link rel="prefetch" href="/assets/js/174.3c2df318.js"><link rel="prefetch" href="/assets/js/175.d2690cdb.js"><link rel="prefetch" href="/assets/js/176.9ca64696.js"><link rel="prefetch" href="/assets/js/177.76f3271d.js"><link rel="prefetch" href="/assets/js/178.d7d9def2.js"><link rel="prefetch" href="/assets/js/179.b5644743.js"><link rel="prefetch" href="/assets/js/18.31fe7ecd.js"><link rel="prefetch" href="/assets/js/180.7592d5ef.js"><link rel="prefetch" href="/assets/js/181.5cb77d35.js"><link rel="prefetch" href="/assets/js/182.6fa5633c.js"><link rel="prefetch" href="/assets/js/183.b3a53d1b.js"><link rel="prefetch" href="/assets/js/184.3815c537.js"><link rel="prefetch" href="/assets/js/185.bcf4ab71.js"><link rel="prefetch" href="/assets/js/186.1cc02f6d.js"><link rel="prefetch" href="/assets/js/187.8b425fb7.js"><link rel="prefetch" href="/assets/js/188.44ccbd02.js"><link rel="prefetch" href="/assets/js/189.353b35e3.js"><link rel="prefetch" href="/assets/js/19.520992d5.js"><link rel="prefetch" href="/assets/js/191.788ecc2d.js"><link rel="prefetch" href="/assets/js/192.712a164e.js"><link rel="prefetch" href="/assets/js/193.da58aba3.js"><link rel="prefetch" href="/assets/js/194.6b1b1f4d.js"><link rel="prefetch" href="/assets/js/195.c31d5c39.js"><link rel="prefetch" href="/assets/js/196.f6670c4d.js"><link rel="prefetch" href="/assets/js/197.5a1f50ab.js"><link rel="prefetch" href="/assets/js/2.ab565158.js"><link rel="prefetch" href="/assets/js/20.69e29cdc.js"><link rel="prefetch" href="/assets/js/21.2fd424ad.js"><link rel="prefetch" href="/assets/js/22.d4c0be54.js"><link rel="prefetch" href="/assets/js/23.4bb90ecc.js"><link rel="prefetch" href="/assets/js/24.c01be6b2.js"><link rel="prefetch" href="/assets/js/25.c8833687.js"><link rel="prefetch" href="/assets/js/26.8042b555.js"><link rel="prefetch" href="/assets/js/27.0d5fa4c0.js"><link rel="prefetch" href="/assets/js/28.f9735b8b.js"><link rel="prefetch" href="/assets/js/29.3af53626.js"><link rel="prefetch" href="/assets/js/30.5f1b56d1.js"><link rel="prefetch" href="/assets/js/31.544b2649.js"><link rel="prefetch" href="/assets/js/32.aa321988.js"><link rel="prefetch" href="/assets/js/33.6aba2c86.js"><link rel="prefetch" href="/assets/js/34.e1bbff24.js"><link rel="prefetch" href="/assets/js/35.233f76e0.js"><link rel="prefetch" href="/assets/js/36.cb773972.js"><link rel="prefetch" href="/assets/js/37.393d9c59.js"><link rel="prefetch" href="/assets/js/38.e2d530c5.js"><link rel="prefetch" href="/assets/js/39.acaf1cc0.js"><link rel="prefetch" href="/assets/js/40.358f731e.js"><link rel="prefetch" href="/assets/js/41.ded24b7e.js"><link rel="prefetch" href="/assets/js/42.b9f683c3.js"><link rel="prefetch" href="/assets/js/43.c8fb3e66.js"><link rel="prefetch" href="/assets/js/44.633142da.js"><link rel="prefetch" href="/assets/js/45.6095e772.js"><link rel="prefetch" href="/assets/js/46.421d8c7a.js"><link rel="prefetch" href="/assets/js/47.da50fe47.js"><link rel="prefetch" href="/assets/js/48.15ff5726.js"><link rel="prefetch" href="/assets/js/49.b662e624.js"><link rel="prefetch" href="/assets/js/5.c1b8a209.js"><link rel="prefetch" href="/assets/js/50.a8bc75df.js"><link rel="prefetch" href="/assets/js/51.51e36ae7.js"><link rel="prefetch" href="/assets/js/52.54cc6e51.js"><link rel="prefetch" href="/assets/js/53.4173561d.js"><link rel="prefetch" href="/assets/js/54.7cab8416.js"><link rel="prefetch" href="/assets/js/55.3d7317d3.js"><link rel="prefetch" href="/assets/js/56.3c22255b.js"><link rel="prefetch" href="/assets/js/57.18e46e30.js"><link rel="prefetch" href="/assets/js/58.aad57f31.js"><link rel="prefetch" href="/assets/js/59.7897f6a7.js"><link rel="prefetch" href="/assets/js/6.3131f88a.js"><link rel="prefetch" href="/assets/js/60.5cd0051a.js"><link rel="prefetch" href="/assets/js/61.d9606403.js"><link rel="prefetch" href="/assets/js/62.aede9df0.js"><link rel="prefetch" href="/assets/js/63.2c30e554.js"><link rel="prefetch" href="/assets/js/64.18228ab7.js"><link rel="prefetch" href="/assets/js/65.27cb3fba.js"><link rel="prefetch" href="/assets/js/66.2fa6c2dc.js"><link rel="prefetch" href="/assets/js/67.d274a8df.js"><link rel="prefetch" href="/assets/js/68.3069cfcf.js"><link rel="prefetch" href="/assets/js/69.4c28600f.js"><link rel="prefetch" href="/assets/js/7.89e6165d.js"><link rel="prefetch" href="/assets/js/70.4175440c.js"><link rel="prefetch" href="/assets/js/71.2ee6b435.js"><link rel="prefetch" href="/assets/js/72.c75e3bb8.js"><link rel="prefetch" href="/assets/js/73.6f8b8211.js"><link rel="prefetch" href="/assets/js/74.6c7720cf.js"><link rel="prefetch" href="/assets/js/75.cccfb229.js"><link rel="prefetch" href="/assets/js/76.f022e5da.js"><link rel="prefetch" href="/assets/js/77.dab46206.js"><link rel="prefetch" href="/assets/js/78.ca574b2a.js"><link rel="prefetch" href="/assets/js/79.3d75e618.js"><link rel="prefetch" href="/assets/js/80.091749b1.js"><link rel="prefetch" href="/assets/js/81.14db0e21.js"><link rel="prefetch" href="/assets/js/82.8a2b1809.js"><link rel="prefetch" href="/assets/js/83.84a4b599.js"><link rel="prefetch" href="/assets/js/84.11d7c222.js"><link rel="prefetch" href="/assets/js/85.273d4388.js"><link rel="prefetch" href="/assets/js/86.fb40e20c.js"><link rel="prefetch" href="/assets/js/87.3316639e.js"><link rel="prefetch" href="/assets/js/88.dfc52200.js"><link rel="prefetch" href="/assets/js/89.8d615f6e.js"><link rel="prefetch" href="/assets/js/90.1d9f08bb.js"><link rel="prefetch" href="/assets/js/91.566813e7.js"><link rel="prefetch" href="/assets/js/92.d13c6f41.js"><link rel="prefetch" href="/assets/js/93.845c42a0.js"><link rel="prefetch" href="/assets/js/94.20a37b77.js"><link rel="prefetch" href="/assets/js/95.1a498005.js"><link rel="prefetch" href="/assets/js/96.39fa7f4b.js"><link rel="prefetch" href="/assets/js/97.50f7170e.js"><link rel="prefetch" href="/assets/js/98.dd2e15d6.js"><link rel="prefetch" href="/assets/js/99.ef7ea06a.js"><link rel="prefetch" href="/assets/js/vendors~docsearch.ae6b1de9.js">
    <link rel="stylesheet" href="/assets/css/0.styles.952d6952.css">
  </head>
  <body class="theme-mode-light">
    <div id="app" data-server-rendered="true"><div class="theme-container sidebar-open have-rightmenu"><header class="navbar blur"><div title="目录" class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"><img src="https://cdn.jsdelivr.net/gh/cmty256/imgs-blog@main/logo/白云.38zbldnhh180.jpg" alt="ShuangChenYue" class="logo"> <span class="site-name can-hide">ShuangChenYue</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/" class="nav-link">首页</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="CPP语言" class="dropdown-title"><!----> <span class="title" style="display:;">CPP语言</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/c5bdd8/" class="nav-link">Cpp之旅</a></li><li class="dropdown-item"><!----> <a href="/pages/279e62/" class="nav-link">Cpp专栏</a></li><li class="dropdown-item"><!----> <a href="/pages/801755/" class="nav-link">Effective_CPP</a></li><li class="dropdown-item"><!----> <a href="/pages/6b2468/" class="nav-link">muduo网络库</a></li><li class="dropdown-item"><!----> <a href="/pages/5f8c9f/" class="nav-link">Unix环境高级编程</a></li><li class="dropdown-item"><!----> <a href="/pages/3f1d21/" class="nav-link">Cpp提高编程</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="计算机基础" class="dropdown-title"><!----> <span class="title" style="display:;">计算机基础</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/7b1cb2/" class="nav-link">计算机网络</a></li><li class="dropdown-item"><!----> <a href="/pages/6048a8/" class="nav-link">操作系统</a></li><li class="dropdown-item"><!----> <a href="/pages/3b34ba/" class="nav-link">数据结构</a></li><li class="dropdown-item"><!----> <a href="/pages/412fe7/" class="nav-link">Linux</a></li><li class="dropdown-item"><!----> <a href="/pages/2dcfa1/" class="nav-link">算法</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="数据库" class="dropdown-title"><!----> <span class="title" style="display:;">数据库</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/efa3f2/" class="nav-link">基础篇</a></li><li class="dropdown-item"><!----> <a href="/pages/ccc445/" class="nav-link">MySql</a></li><li class="dropdown-item"><!----> <a href="/pages/54616e/" class="nav-link">Redis</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="嵌入式软件开发" class="dropdown-title"><!----> <span class="title" style="display:;">嵌入式软件开发</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/d142c2/" class="nav-link">电子嵌入式通信协议</a></li><li class="dropdown-item"><!----> <a href="/pages/4c6bf3/" class="nav-link">深入浅出SSD</a></li><li class="dropdown-item"><!----> <a href="/pages/d3f36a/" class="nav-link">文件系统</a></li><li class="dropdown-item"><!----> <a href="/pages/e0cca7/" class="nav-link">汇编语言</a></li><li class="dropdown-item"><!----> <a href="/pages/fab2d7/" class="nav-link">STM32</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="开发日常" class="dropdown-title"><!----> <span class="title" style="display:;">开发日常</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/e472d1/" class="nav-link">随笔（持续更新）</a></li><li class="dropdown-item"><!----> <a href="/pages/71f6ae/" class="nav-link">Git知识总结</a></li><li class="dropdown-item"><!----> <a href="/pages/db6fb8/" class="nav-link">Git备忘清单</a></li><li class="dropdown-item"><!----> <a href="/pages/e1081f/" class="nav-link">Git 创建删除远程分支</a></li><li class="dropdown-item"><!----> <a href="/pages/777b8a/" class="nav-link">nvm使用小结</a></li><li class="dropdown-item"><!----> <a href="/pages/ee770e/" class="nav-link">虚拟机固定 IP 地址</a></li><li class="dropdown-item"><!----> <a href="/pages/1ab9a6/" class="nav-link">Shell 脚本学习笔记</a></li><li class="dropdown-item"><!----> <a href="/pages/411aa4/" class="nav-link">VScode 插件 CodeGeeX 使用教程</a></li><li class="dropdown-item"><!----> <a href="/pages/0d525d/" class="nav-link">KylinV10 将项目上传至 Github教程</a></li><li class="dropdown-item"><!----> <a href="/pages/907786/" class="nav-link">KylinV10 安装 MySQL 教程（可防踩雷）</a></li><li class="dropdown-item"><!----> <a href="/pages/a2d21e/" class="nav-link">kylinV10-SP1 安装 QT</a></li><li class="dropdown-item"><!----> <a href="/pages/b561cf/" class="nav-link">高并发内存池</a></li><li class="dropdown-item"><!----> <a href="/pages/6ab6d1/" class="nav-link">USBGUARD 项目编译环境配置</a></li><li class="dropdown-item"><!----> <a href="/pages/883f02/" class="nav-link">Power_Destory 项目</a></li><li class="dropdown-item"><!----> <a href="/pages/479472/" class="nav-link">U 盘清除工具编译教程</a></li><li class="dropdown-item"><!----> <a href="/pages/9c4241/" class="nav-link">个人博客代码推送教程</a></li><li class="dropdown-item"><!----> <a href="/pages/3ad765/" class="nav-link">SVN Trunk Branches的Merge操作</a></li><li class="dropdown-item"><!----> <a href="/pages/0c0ca8/" class="nav-link">如何高效阅读嵌入式项目代码</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="前端学习" class="dropdown-title"><!----> <span class="title" style="display:;">前端学习</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/99897f/" class="nav-link">HTML与CSS</a></li><li class="dropdown-item"><!----> <a href="/pages/51542d/" class="nav-link">JS学习</a></li><li class="dropdown-item"><!----> <a href="/pages/803f9d/" class="nav-link">Vue3入门</a></li><li class="dropdown-item"><!----> <a href="/pages/ca4cfb/" class="nav-link">Vue3进阶</a></li><li class="dropdown-item"><!----> <a href="/pages/50e8d3/" class="nav-link">黑马Vue3</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="MFC" class="dropdown-title"><!----> <span class="title" style="display:;">MFC</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/a4b108/" class="nav-link">MFC编程随记</a></li><li class="dropdown-item"><!----> <a href="/pages/41acbd/" class="nav-link">MFC实现ini配置文件的读取</a></li><li class="dropdown-item"><!----> <a href="/pages/951a7a/" class="nav-link">MFC实现点击列表头排序</a></li><li class="dropdown-item"><!----> <a href="/pages/a8598f/" class="nav-link">贴图法美化Button按钮</a></li><li class="dropdown-item"><!----> <a href="/pages/054516/" class="nav-link">MFC使用细节</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="闪存" class="dropdown-title"><!----> <span class="title" style="display:;">闪存</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/b925b8/" class="nav-link">如何高效阅读嵌入式项目代码</a></li><li class="dropdown-item"><!----> <a href="/pages/28ec23/" class="nav-link">NAND Flash</a></li><li class="dropdown-item"><!----> <a href="/pages/62bf40/" class="nav-link">ARM 处理器</a></li><li class="dropdown-item"><!----> <a href="/pages/1a9374/" class="nav-link">嵌入式基础知识-存储器</a></li><li class="dropdown-item"><!----> <a href="/pages/aac5e3/" class="nav-link">闪存存储和制造技术概述</a></li><li class="dropdown-item"><!----> <a href="/pages/8f6056/" class="nav-link">芯片IO驱动力</a></li><li class="dropdown-item"><!----> <a href="/pages/d146b8/" class="nav-link">主流先进封装技术介绍</a></li><li class="dropdown-item"><!----> <a href="/pages/16f0ba/" class="nav-link">NAND Flash基础</a></li><li class="dropdown-item"><!----> <a href="/pages/90d8d0/" class="nav-link">基于PA算法的FTL引导</a></li><li class="dropdown-item"><!----> <a href="/pages/eb672b/" class="nav-link">SD逻辑擦除和物理擦除</a></li><li class="dropdown-item"><!----> <a href="/pages/747121/" class="nav-link">NAND Flash的SDR、ONFI、DDR接口</a></li><li class="dropdown-item"><!----> <a href="/pages/1eb351/" class="nav-link">【详解】Nand Flash必看知识</a></li><li class="dropdown-item"><!----> <a href="/pages/d2512a/" aria-current="page" class="nav-link router-link-exact-active router-link-active">【两万字详解】Nand Flash必看知识</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="面经" class="dropdown-title"><!----> <span class="title" style="display:;">面经</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/d69946/" class="nav-link">虎牙C++技术面经</a></li><li class="dropdown-item"><!----> <a href="/pages/29251d/" class="nav-link">金山一面复习</a></li><li class="dropdown-item"><!----> <a href="/pages/c7c01f/" class="nav-link">完美世界秋招 C++ 游戏开发面经(Cpp部分)</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="其它" class="dropdown-title"><!----> <span class="title" style="display:;">其它</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/fa256e/" class="nav-link">博客搭建</a></li><li class="dropdown-item"><!----> <a href="/pages/335531/" class="nav-link">网站收藏箱</a></li></ul></div></div> <!----></nav></div></header> <div class="sidebar-mask"></div> <div class="sidebar-hover-trigger"></div> <aside class="sidebar" style="display:none;"><!----> <nav class="nav-links"><div class="nav-item"><a href="/" class="nav-link">首页</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="CPP语言" class="dropdown-title"><!----> <span class="title" style="display:;">CPP语言</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/c5bdd8/" class="nav-link">Cpp之旅</a></li><li class="dropdown-item"><!----> <a href="/pages/279e62/" class="nav-link">Cpp专栏</a></li><li class="dropdown-item"><!----> <a href="/pages/801755/" class="nav-link">Effective_CPP</a></li><li class="dropdown-item"><!----> <a href="/pages/6b2468/" class="nav-link">muduo网络库</a></li><li class="dropdown-item"><!----> <a href="/pages/5f8c9f/" class="nav-link">Unix环境高级编程</a></li><li class="dropdown-item"><!----> <a href="/pages/3f1d21/" class="nav-link">Cpp提高编程</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="计算机基础" class="dropdown-title"><!----> <span class="title" style="display:;">计算机基础</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/7b1cb2/" class="nav-link">计算机网络</a></li><li class="dropdown-item"><!----> <a href="/pages/6048a8/" class="nav-link">操作系统</a></li><li class="dropdown-item"><!----> <a href="/pages/3b34ba/" class="nav-link">数据结构</a></li><li class="dropdown-item"><!----> <a href="/pages/412fe7/" class="nav-link">Linux</a></li><li class="dropdown-item"><!----> <a href="/pages/2dcfa1/" class="nav-link">算法</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="数据库" class="dropdown-title"><!----> <span class="title" style="display:;">数据库</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/efa3f2/" class="nav-link">基础篇</a></li><li class="dropdown-item"><!----> <a href="/pages/ccc445/" class="nav-link">MySql</a></li><li class="dropdown-item"><!----> <a href="/pages/54616e/" class="nav-link">Redis</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="嵌入式软件开发" class="dropdown-title"><!----> <span class="title" style="display:;">嵌入式软件开发</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/d142c2/" class="nav-link">电子嵌入式通信协议</a></li><li class="dropdown-item"><!----> <a href="/pages/4c6bf3/" class="nav-link">深入浅出SSD</a></li><li class="dropdown-item"><!----> <a href="/pages/d3f36a/" class="nav-link">文件系统</a></li><li class="dropdown-item"><!----> <a href="/pages/e0cca7/" class="nav-link">汇编语言</a></li><li class="dropdown-item"><!----> <a href="/pages/fab2d7/" class="nav-link">STM32</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="开发日常" class="dropdown-title"><!----> <span class="title" style="display:;">开发日常</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/e472d1/" class="nav-link">随笔（持续更新）</a></li><li class="dropdown-item"><!----> <a href="/pages/71f6ae/" class="nav-link">Git知识总结</a></li><li class="dropdown-item"><!----> <a href="/pages/db6fb8/" class="nav-link">Git备忘清单</a></li><li class="dropdown-item"><!----> <a href="/pages/e1081f/" class="nav-link">Git 创建删除远程分支</a></li><li class="dropdown-item"><!----> <a href="/pages/777b8a/" class="nav-link">nvm使用小结</a></li><li class="dropdown-item"><!----> <a href="/pages/ee770e/" class="nav-link">虚拟机固定 IP 地址</a></li><li class="dropdown-item"><!----> <a href="/pages/1ab9a6/" class="nav-link">Shell 脚本学习笔记</a></li><li class="dropdown-item"><!----> <a href="/pages/411aa4/" class="nav-link">VScode 插件 CodeGeeX 使用教程</a></li><li class="dropdown-item"><!----> <a href="/pages/0d525d/" class="nav-link">KylinV10 将项目上传至 Github教程</a></li><li class="dropdown-item"><!----> <a href="/pages/907786/" class="nav-link">KylinV10 安装 MySQL 教程（可防踩雷）</a></li><li class="dropdown-item"><!----> <a href="/pages/a2d21e/" class="nav-link">kylinV10-SP1 安装 QT</a></li><li class="dropdown-item"><!----> <a href="/pages/b561cf/" class="nav-link">高并发内存池</a></li><li class="dropdown-item"><!----> <a href="/pages/6ab6d1/" class="nav-link">USBGUARD 项目编译环境配置</a></li><li class="dropdown-item"><!----> <a href="/pages/883f02/" class="nav-link">Power_Destory 项目</a></li><li class="dropdown-item"><!----> <a href="/pages/479472/" class="nav-link">U 盘清除工具编译教程</a></li><li class="dropdown-item"><!----> <a href="/pages/9c4241/" class="nav-link">个人博客代码推送教程</a></li><li class="dropdown-item"><!----> <a href="/pages/3ad765/" class="nav-link">SVN Trunk Branches的Merge操作</a></li><li class="dropdown-item"><!----> <a href="/pages/0c0ca8/" class="nav-link">如何高效阅读嵌入式项目代码</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="前端学习" class="dropdown-title"><!----> <span class="title" style="display:;">前端学习</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/99897f/" class="nav-link">HTML与CSS</a></li><li class="dropdown-item"><!----> <a href="/pages/51542d/" class="nav-link">JS学习</a></li><li class="dropdown-item"><!----> <a href="/pages/803f9d/" class="nav-link">Vue3入门</a></li><li class="dropdown-item"><!----> <a href="/pages/ca4cfb/" class="nav-link">Vue3进阶</a></li><li class="dropdown-item"><!----> <a href="/pages/50e8d3/" class="nav-link">黑马Vue3</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="MFC" class="dropdown-title"><!----> <span class="title" style="display:;">MFC</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/a4b108/" class="nav-link">MFC编程随记</a></li><li class="dropdown-item"><!----> <a href="/pages/41acbd/" class="nav-link">MFC实现ini配置文件的读取</a></li><li class="dropdown-item"><!----> <a href="/pages/951a7a/" class="nav-link">MFC实现点击列表头排序</a></li><li class="dropdown-item"><!----> <a href="/pages/a8598f/" class="nav-link">贴图法美化Button按钮</a></li><li class="dropdown-item"><!----> <a href="/pages/054516/" class="nav-link">MFC使用细节</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="闪存" class="dropdown-title"><!----> <span class="title" style="display:;">闪存</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/b925b8/" class="nav-link">如何高效阅读嵌入式项目代码</a></li><li class="dropdown-item"><!----> <a href="/pages/28ec23/" class="nav-link">NAND Flash</a></li><li class="dropdown-item"><!----> <a href="/pages/62bf40/" class="nav-link">ARM 处理器</a></li><li class="dropdown-item"><!----> <a href="/pages/1a9374/" class="nav-link">嵌入式基础知识-存储器</a></li><li class="dropdown-item"><!----> <a href="/pages/aac5e3/" class="nav-link">闪存存储和制造技术概述</a></li><li class="dropdown-item"><!----> <a href="/pages/8f6056/" class="nav-link">芯片IO驱动力</a></li><li class="dropdown-item"><!----> <a href="/pages/d146b8/" class="nav-link">主流先进封装技术介绍</a></li><li class="dropdown-item"><!----> <a href="/pages/16f0ba/" class="nav-link">NAND Flash基础</a></li><li class="dropdown-item"><!----> <a href="/pages/90d8d0/" class="nav-link">基于PA算法的FTL引导</a></li><li class="dropdown-item"><!----> <a href="/pages/eb672b/" class="nav-link">SD逻辑擦除和物理擦除</a></li><li class="dropdown-item"><!----> <a href="/pages/747121/" class="nav-link">NAND Flash的SDR、ONFI、DDR接口</a></li><li class="dropdown-item"><!----> <a href="/pages/1eb351/" class="nav-link">【详解】Nand Flash必看知识</a></li><li class="dropdown-item"><!----> <a href="/pages/d2512a/" aria-current="page" class="nav-link router-link-exact-active router-link-active">【两万字详解】Nand Flash必看知识</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="面经" class="dropdown-title"><!----> <span class="title" style="display:;">面经</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/d69946/" class="nav-link">虎牙C++技术面经</a></li><li class="dropdown-item"><!----> <a href="/pages/29251d/" class="nav-link">金山一面复习</a></li><li class="dropdown-item"><!----> <a href="/pages/c7c01f/" class="nav-link">完美世界秋招 C++ 游戏开发面经(Cpp部分)</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="其它" class="dropdown-title"><!----> <span class="title" style="display:;">其它</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/fa256e/" class="nav-link">博客搭建</a></li><li class="dropdown-item"><!----> <a href="/pages/335531/" class="nav-link">网站收藏箱</a></li></ul></div></div> <!----></nav>  <ul class="sidebar-links"><li><a href="/pages/1a9374/" class="sidebar-link">嵌入式基础知识——存储器</a></li><li><a href="/pages/b925b8/" class="sidebar-link">如何高效阅读嵌入式项目代码</a></li><li><a href="/pages/aac5e3/" class="sidebar-link">闪存存储和制造技术概述</a></li><li><a href="/pages/8f6056/" class="sidebar-link">芯片 IO 驱动力</a></li><li><a href="/pages/62bf40/" class="sidebar-link">ARM处理器</a></li><li><a href="/pages/28ec23/" class="sidebar-link">Nand Flash</a></li><li><a href="/pages/d146b8/" class="sidebar-link">主流先进封装技术介绍</a></li><li><a href="/pages/16f0ba/" class="sidebar-link">Nand Flash 基础</a></li><li><a href="/pages/90d8d0/" class="sidebar-link">基于PA算法的FTL引导</a></li><li><a href="/pages/eb672b/" class="sidebar-link">SD 逻辑擦除和物理擦除</a></li><li><a href="/pages/747121/" class="sidebar-link">NAND Flash 的 SDR、ONFI、DDR 接口</a></li><li><a href="/pages/1eb351/" class="sidebar-link">【详解】Nand Flash 必看知识 1</a></li><li><a href="/pages/d2512a/" aria-current="page" class="active sidebar-link">【两万字详解】Nand Flash 必看知识</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level2"><a href="/pages/d2512a/#_1-名词解释" class="sidebar-link">1. 名词解释</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_1-1-non-volatile-memory-非易失性存储器" class="sidebar-link">1.1 Non-Volatile Memory （非易失性存储器）</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_1-2-otp-一次性可编程存储器" class="sidebar-link">1.2 OTP 一次性可编程存储器</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_1-3-nda-保密协议" class="sidebar-link">1.3 NDA 保密协议</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_1-4-datasheet-数据手册和-specification-规范" class="sidebar-link">1.4 Datasheet 数据手册和 Specification 规范</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_1-5-bad-block-management-坏-块管理" class="sidebar-link">1.5 (Bad)Block Management（坏）块管理</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_1-6-wear-leveling-负载平衡" class="sidebar-link">1.6 Wear-Leveling 负载平衡</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_1-7-ecc-错误校验码" class="sidebar-link">1.7 ECC 错误校验码</a></li></ul></li><li class="sidebar-sub-header level2"><a href="/pages/d2512a/#_2-硬件特性" class="sidebar-link">2. 硬件特性</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-1-什么是-flash" class="sidebar-link">2.1 什么是 Flash</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-2-flash-的硬件实现机制" class="sidebar-link">2.2 Flash 的硬件实现机制</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-3-什么是-nand-flash" class="sidebar-link">2.3 什么是 Nand Flash</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-3-1-结构和存储方式" class="sidebar-link">2.3.1 结构和存储方式</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-3-2-性能" class="sidebar-link">2.3.2 性能</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-3-3-存储密度和成本" class="sidebar-link">2.3.3 存储密度和成本</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-3-4-应用场景" class="sidebar-link">2.3.4 应用场景</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-3-5-优缺点总结" class="sidebar-link">2.3.5 优缺点总结</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-4-nand-flash-的详细分类" class="sidebar-link">2.4 Nand Flash 的详细分类</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-5-slc-和-mlc-的实现机制" class="sidebar-link">2.5 SLC 和 MLC 的实现机制</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-5-1-slc-single-level-cell" class="sidebar-link">2.5.1 SLC（Single Level Cell）</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-5-2-mlc-multi-level-cell" class="sidebar-link">2.5.2 MLC（Multi Level Cell）</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-6-如何识别-slc-还是-mlc" class="sidebar-link">2.6 如何识别 SLC 还是 MLC</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#例子" class="sidebar-link">例子：</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-7-nand-flash-数据存储单元的整体架构" class="sidebar-link">2.7 Nand Flash 数据存储单元的整体架构</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-8-nand-flash的物理存储单元的阵列组织结构" class="sidebar-link">2.8 Nand Flash的物理存储单元的阵列组织结构</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-8-1-block-块" class="sidebar-link">2.8.1 Block 块</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-8-2-page-页" class="sidebar-link">2.8.2 Page 页</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-8-3-oob-redundant-area-spare-area" class="sidebar-link">2.8.3  oob / Redundant Area / Spare Area</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-9-flash-名称的由来" class="sidebar-link">2.9 Flash 名称的由来</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-10-flash-相对于普通设备的特殊性" class="sidebar-link">2.10 Flash 相对于普通设备的特殊性</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-11-nand-flash-的位反转特性" class="sidebar-link">2.11 Nand Flash 的位反转特性</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-12-nand-flash-位反转的原因" class="sidebar-link">2.12 Nand Flash 位反转的原因</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-13-nand-flash-位反转的影响" class="sidebar-link">2.13 Nand Flash 位反转的影响</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-14-nand-flash-位反转的类型和解决办法" class="sidebar-link">2.14 Nand Flash 位反转的类型和解决办法</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-15-nand-flash-引脚-pin-的说明" class="sidebar-link">2.15 Nand Flash 引脚(Pin)的说明</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-16-为何需要-ale-和-cle" class="sidebar-link">2.16 为何需要 ALE 和 CLE</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-17-nand-flash-只有-8个-i-o-引脚的好处" class="sidebar-link">2.17 Nand Flash 只有 8个 I/O 引脚的好处</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-18-nand-flash-的一些典型-typical-的特性" class="sidebar-link">2.18 Nand Flash 的一些典型 (typical) 的特性</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-19-nand-flash-控制器与-nand-flash-芯片" class="sidebar-link">2.19 Nand Flash 控制器与 Nand Flash 芯片</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-20-nand-flash-中的特殊硬件结构" class="sidebar-link">2.20 Nand Flash 中的特殊硬件结构</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-21-nand-flash-中的坏块-bad-block" class="sidebar-link">2.21 Nand Flash 中的坏块 (Bad Block)</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-21-1-坏块的分类" class="sidebar-link">2.21.1 坏块的分类</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-21-2-坏块的标记" class="sidebar-link">2.21.2 坏块的标记</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-21-3-坏块的管理" class="sidebar-link">2.21.3 坏块的管理</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-21-4-坏块的比例" class="sidebar-link">2.21.4 坏块的比例</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-22-nand-flash-中页的访问顺序" class="sidebar-link">2.22 Nand Flash 中页的访问顺序</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-23-常见的-nand-flash-的操作" class="sidebar-link">2.23 常见的 Nand Flash 的操作</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-23-1-页编程-page-program-注意事项" class="sidebar-link">2.23.1 页编程（Page Program）注意事项</a></li><li class="sidebar-sub-header level4"><a href="/pages/d2512a/#_2-23-2-读-read-操作过程详解" class="sidebar-link">2.23.2 读（Read）操作过程详解</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-24-如何计算出我们要传入的行地址和列地址" class="sidebar-link">2.24 如何计算出我们要传入的行地址和列地址</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_2-25-读操作过程的解释" class="sidebar-link">2.25 读操作过程的解释</a></li></ul></li><li class="sidebar-sub-header level2"><a href="/pages/d2512a/#_3-nand-flash-的一些高级特性" class="sidebar-link">3. Nand Flash 的一些高级特性</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_3-1-nand-flash-的-unique-id" class="sidebar-link">3.1 Nand Flash 的 Unique ID</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_3-2-片选无关-ce-don-t-care-技术" class="sidebar-link">3.2 片选无关(CE don’t-care)技术</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_3-3-带-edc-的拷回操作以及-sector-的定义-copy-back-operation-with-edc-sector-definition-for-edc" class="sidebar-link">3.3 带 EDC 的拷回操作以及 Sector 的定义（Copy-Back Operation with EDC &amp; Sector Definition for EDC）</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_3-4-多片同时编程-simultaneously-program-multi-plane" class="sidebar-link">3.4 多片同时编程(Simultaneously Program Multi Plane)</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_3-5-交错页编程-interleave-page-program" class="sidebar-link">3.5 交错页编程（Interleave Page Program）</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_3-6-随机输出页内数据-random-data-output-in-a-page" class="sidebar-link">3.6 随机输出页内数据（Random Data Output In a Page）</a></li></ul></li><li class="sidebar-sub-header level2"><a href="/pages/d2512a/#_4-nand-flash-相关规范-onfi-和-lba" class="sidebar-link">4. Nand Flash 相关规范 -- ONFI 和 LBA</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_4-1-onfi-是什么" class="sidebar-link">4.1 ONFI 是什么</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_4-2-onfi-block-abstracted-nand" class="sidebar-link">4.2 ONFI Block Abstracted NAND</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_4-3-onfi-的好处" class="sidebar-link">4.3 ONFI 的好处</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_4-4-lba-规范是什么" class="sidebar-link">4.4 LBA 规范是什么</a></li><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_4-5-onfi-和-lba-的区别和联系" class="sidebar-link">4.5 ONFI 和 LBA 的区别和联系</a></li></ul></li><li class="sidebar-sub-header level2"><a href="/pages/d2512a/#_5-内存技术设备-mtd-memory-technology-device" class="sidebar-link">5. 内存技术设备 MTD（Memory Technology Device）</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/pages/d2512a/#_5-1-读操作的硬件到软件的映射" class="sidebar-link">5.1 读操作的硬件到软件的映射</a></li></ul></li></ul></li></ul> </aside> <div><main class="page"><div class="theme-vdoing-wrapper "><div class="articleInfo-wrap" data-v-06225672><div class="articleInfo" data-v-06225672><ul class="breadcrumbs" data-v-06225672><li data-v-06225672><a href="/" title="首页" class="iconfont icon-home router-link-active" data-v-06225672></a></li> <li data-v-06225672><span data-v-06225672>闪存</span></li></ul> <div class="info" data-v-06225672><div title="作者" class="author iconfont icon-touxiang" data-v-06225672><a href="javascript:;" data-v-06225672>霜晨月</a></div> <div title="创建时间" class="date iconfont icon-riqi" data-v-06225672><a href="javascript:;" data-v-06225672>2025-07-09</a></div> <!----></div></div></div> <!----> <div class="content-wrapper"><div class="right-menu-wrapper"><div class="right-menu-margin"><div class="right-menu-title">目录</div> <div class="right-menu-content"></div></div></div> <h1><img src="">【两万字详解】Nand Flash 必看知识<!----></h1> <!----> <div class="theme-vdoing-content content__default"><h1 id="【详解】nand-flash-必看知识1"><a href="#【详解】nand-flash-必看知识1" class="header-anchor">#</a> 【详解】Nand Flash 必看知识1</h1> <blockquote><p>==<strong>常见缩略词</strong>==</p> <ul><li>BBM (BBM)：Bad Block Management 坏块管理</li> <li>BBT (BBT)：Bad Block Table 坏块表</li> <li>ECC (ECC)：Error Correction Code 错误校验码</li> <li>E2PROM/EEPROM (EEPROM)：Electrically Erasable Programmable Read-Only Memory：电可擦只读存储器</li> <li>MLC (MLC)：Multi Level Cell 多层单元</li> <li>MOSFET (MOSFET)：Metal-Oxide–Semiconductor Field Effect Transistor 金属氧化物半导体场效应晶体管</li> <li>MTD (MTD)：Memory Technology Device 内存技术设备</li> <li>NVM (NVM)：Non-Volatile Memory</li> <li>NDA (NDA)：None-Disclosure Agreement 非公开协议，保密协议</li> <li>OTP (OTP)：One Time Programmable 一次性可编程（存储器）</li> <li>SLC (SLC)：Single Level Cell 单层单元，单层式存储</li></ul></blockquote> <h2 id="_1-名词解释"><a href="#_1-名词解释" class="header-anchor">#</a> 1. 名词解释</h2> <h3 id="_1-1-non-volatile-memory-非易失性存储器"><a href="#_1-1-non-volatile-memory-非易失性存储器" class="header-anchor">#</a> 1.1 Non-Volatile Memory （非易失性存储器）</h3> <p>NVM，即 NV （RAM）Memory，断电数据也不会丢失的存储器，比如 Nand Flash，Nor Flash，硬盘等等。于此相对的是，断电了数据会丢失的存储器，比如 DRAM 等。</p> <h3 id="_1-2-otp-一次性可编程存储器"><a href="#_1-2-otp-一次性可编程存储器" class="header-anchor">#</a> 1.2 OTP 一次性可编程存储器</h3> <p>OTP，一种非易失性存储器，但是只允许一次性写入数据，写入（或称烧写）数据之后，就不能修改了。</p> <p>OTP的好处或者说用途是，常用于写入一些和芯片相关的一些特定数据，用于加密的一些数据等。</p> <p>与一次性写入数据的 OTP 相对应的是，像 Nand Flash，硬盘等存储器，可以被多次写入数据。只要硬盘这类的存储器没坏，你愿意写入几次就写入几次，而 OTP 就只能写入一次，就没法再修改里面的数据了。</p> <h3 id="_1-3-nda-保密协议"><a href="#_1-3-nda-保密协议" class="header-anchor">#</a> 1.3 NDA 保密协议</h3> <p>NDA，中文可以翻译为，非公开协议，保密协议。</p> <p><strong>常用于这种情况：</strong> 某家厂商的某种技术或资料，是保密的，不希望公开的。但是呢，如果你要用他家的芯片之类的东西，在开发过程中，又必须得到对应的技术和资料，才能开发产品，所以，他就会要求和你签订这样的 NDA 协议，意思就是，你可以用我的技术和资料，但是你不能公开给（我未授权的）其他人。如果非法泄露我的机密技术，那势必要走法律程序控告你之类的。</p> <h3 id="_1-4-datasheet-数据手册和-specification-规范"><a href="#_1-4-datasheet-数据手册和-specification-规范" class="header-anchor">#</a> 1.4 Datasheet 数据手册和 Specification 规范</h3> <ul><li>英文 datasheet，中文一般翻译为数据手册。
<ul><li>指的是对应某个硬件，多为芯片，的功能说明，定义了如何操作该硬件，达到你要的功能，这其中主要包括芯片中的相关寄存器的定义，如何发送命令，发送什么命令，以此来操作此硬件等等。</li></ul></li> <li>英文 Specification，引文常缩写为Spec.，中文一般翻译为规范。
<ul><li>多指某个组织（盈利的或非盈利的），定义了一些规矩，如果你要用某种东西，在计算机领域，常常指的是某硬件和相关的软件协议，就要按照此规矩来操作，这个组织保证你只要实现了此规范，设备就能按照你所期望的运行，能够实现对应的功能，而你的芯片实现了此规范，就叫做，是和此规范兼容（compatible）的。</li></ul></li></ul> <h3 id="_1-5-bad-block-management-坏-块管理"><a href="#_1-5-bad-block-management-坏-块管理" class="header-anchor">#</a> 1.5 (Bad)Block Management（坏）块管理</h3> <p>Nand Flash 由于其物理特性，只有有限的擦写次数，超过那个次数，基本上就是坏了。在使用过程中，有些 Nand Flash的 block 会出现被用坏了，当发现了，要及时将此 block 标注为坏块，不再使用。于此相关的管理工作，属于 Nand Flash 的坏块管理的一部分工作。</p> <h3 id="_1-6-wear-leveling-负载平衡"><a href="#_1-6-wear-leveling-负载平衡" class="header-anchor">#</a> 1.6 Wear-Leveling 负载平衡</h3> <p>Nand Flash 的 block 的管理，还包括负载平衡。</p> <p>正是由于 Nand Flash 的 block 都是有一定寿命限制的，所以如果你每次都往同一个 block 擦除然后写入数据，那么那个 block 就很容易被用坏了，所以我们要去管理一下，将这么多次的对同一个 block 的操作，平均分布到其他一些 block 上面，使得在 block 的使用上，相对较平均，这样相对来说，可以更能充分利用 Nand Flash。以此延长 Nand Flash 的使用寿命或者说更加充分利用 Nand Flash。</p> <h3 id="_1-7-ecc-错误校验码"><a href="#_1-7-ecc-错误校验码" class="header-anchor">#</a> 1.7 ECC 错误校验码</h3> <p>Nand Flash 物理特性上使得其数据读写过程中会发生一定几率的错误，所以要有个对应的错误检测和纠正的机制，于是才有此 ECC，用于数据错误的检测与纠正。Nand Flash 的 ECC。</p> <p>常见的算法有 汉明码（Hamming Code）、BCH（Bose-Chaudhuri-Hocquenghem）码、RS码（Reed-Solomon Code）和LDPC码（Low-Density Parity-Check Code）。</p> <blockquote><ol><li><strong>汉明码（Hamming Code）</strong>：
<ul><li><strong>特点</strong>：能够纠正单比特错误，检测双比特错误。</li> <li><strong>使用场景</strong>：常用于早期的NAND Flash和小容量的存储器。</li> <li><strong>优点</strong>：实现简单，计算开销小。</li> <li><strong>缺点</strong>：纠错能力有限，不适用于高密度存储器。</li></ul></li> <li><strong>BCH码（Bose–Chaudhuri–Hocquenghem Code）</strong>：
<ul><li><strong>特点</strong>：能够纠正多个比特错误，灵活性强。</li> <li><strong>使用场景</strong>：广泛应用于现代NAND Flash存储器，尤其是MLC（多层单元）和TLC（三层单元）NAND。</li> <li><strong>优点</strong>：可以纠正多个随机错误，适应性强。</li> <li><strong>缺点</strong>：实现复杂度较高，计算开销大。</li></ul></li> <li><strong>RS码（Reed-Solomon Code）</strong>：
<ul><li><strong>特点</strong>：能够纠正突发错误，非常适合纠正连续的比特错误。</li> <li><strong>使用场景</strong>：主要用于NAND Flash存储器的控制器中，用于数据块级别的错误校正。</li> <li><strong>优点</strong>：适合纠正突发性错误，纠错能力强。</li> <li><strong>缺点</strong>：实现复杂度高，计算开销大。</li></ul></li> <li><strong>LDPC码（Low-Density Parity-Check Code）</strong>：
<ul><li><strong>特点</strong>：纠错能力强，接近香农极限，适合长码字。</li> <li><strong>使用场景</strong>：逐渐被应用于高密度和高性能的NAND Flash存储器中。</li> <li><strong>优点</strong>：纠错性能优越，特别适用于高数据速率和大容量存储。</li> <li><strong>缺点</strong>：实现复杂度高，解码算法需要较大的计算资源。</li></ul></li></ol> <p>在 NAND Flash 中，选择适当的 ECC 算法主要取决于以下因素：</p> <ul><li><strong>存储密度</strong>：高密度的 NAND Flash（如MLC、TLC）需要更强的纠错能力。</li> <li><strong>性能需求</strong>：实时性要求高的系统需要高效的ECC算法。</li> <li><strong>硬件资源</strong>：嵌入式系统和控制器的处理能力和存储资源限制了可用的 ECC 算法。</li></ul> <p>BCH 码和 LDPC 码是现代 NAND Flash 存储器中最常用的 ECC 算法。BCH 码因其灵活性和较强的纠错能力被广泛应用于大多数 NAND Flash，而 LDPC 码则因其优越的纠错性能逐渐在高性能存储器中占据一席之地。</p></blockquote> <p>这类算法的实现，可以是软件也可以是硬件。不同系统，根据自己的需求，采用对应的软件或者是硬件。</p> <p>相对来说，硬件实现这类 ECC 算法，肯定要比软件速度要快，但是多加了对应的硬件部分，所以成本相对要高些。如果系统对于性能要求不是很高，那么可以采用软件实现这类 ECC 算法，但是由于增加了数据读取和写入前后要做的数据错误检测和纠错，所以性能相对要降低一些，即Nand Flash的读取和写入速度相对会有所影响。</p> <p>其中，Linux 中的软件实现 ECC 算法，即 NAND_ECC_SOFT 模式，就是用的对应的汉明码。</p> <p>而对于目前常见的 MLC 的 Nand Flash 来说，由于容量比较大，动辄 2GB，4GB，8GB 等，常用 BCH 算法。BCH 算法，相对来说，算法比较复杂。</p> <p>笔者由于水平有限，目前仍未完全搞懂 BCH 算法的原理，所以不讲原理。</p> <p>BCH 算法，通常是由对应的 Nand Flash 的 Controller 中，包含对应的硬件 BCH ECC 模块，实现了 BCH 算法，而作为软件方面，需要在读取数据后，写入数据之前，分别操作对应 BCH 相关的寄存器，设置成 BCH 模式，然后读取对应的 BCH 状态寄存器，得知是否有错误，和生成的 BCH 校验码，用于写入。</p> <p>其具体代码是如何操作这些寄存器的，由于是和具体的硬件，具体的 nand flash 的 controller 不同而不同，无法用同一的代码。如果你是 nand flash 驱动开发者，自然会得到对应的起 nand flash 的 controller 部分的 datasheet，按照手册说明，去操作即可。</p> <p>不过，额外说明一下的是，关于 BCH 算法，往往是要从专门的做软件算法的厂家购买的。</p> <h2 id="_2-硬件特性"><a href="#_2-硬件特性" class="header-anchor">#</a> 2. 硬件特性</h2> <h3 id="_2-1-什么是-flash"><a href="#_2-1-什么是-flash" class="header-anchor">#</a> 2.1 什么是 Flash</h3> <p>Flash 全名叫做 Flash Memory，是一种数据存储设备，存储设备有很多类，Flash 属于非易失性存储设备 (Non-volatile Memory Device)，与此相对应的是易失性存储设备 (Volatile Memory Device)。关于什么是非易失性/易失性，从名字中就可以看出，非易失性就是不容易丢失，数据存储在这类设备中，即使断电了，也不会丢失，这类设备，除了 Flash，还有其他比较常见的入硬盘，ROM 等，与此相对的，易失性就是断电了，数据就丢失了，比如大家常用的内存，不论是以前的 SDRAM，DDR SDRAM，还是现在的 DDR2，DDR3 等，都是断电后，数据就没了。</p> <h3 id="_2-2-flash-的硬件实现机制"><a href="#_2-2-flash-的硬件实现机制" class="header-anchor">#</a> 2.2 Flash 的硬件实现机制</h3> <p>Flash 的内部存储是 MOSFET，里面有个悬浮门 (Floating Gate)，是真正存储数据的单元。</p> <p>在 Flash 之前，紫外线可擦除 (uv-erasable) 的 EPROM，就已经采用了 Floating Gate 存储数据这一技术了。</p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.7i0a399g0l.webp" loading="lazy" class="lazy"></p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.7ego5jh747.webp" loading="lazy" class="lazy"></p> <p>数据在 Flash 内存单元中是以电荷 (electrical charge) 形式存储的。存储电荷的多少，取决于图中的外部门（external gate）所被施加的电压，其控制了是向存储单元中冲入电荷还是使其释放电荷。而数据的表示，以所存储的电荷的电压是否超过一个特定的阈值 Vth 来表示，因此，Flash 的存储单元的默认值，不是0（其他常见的存储设备，比如硬盘灯，默认值为0），而是1，而如果将电荷释放掉，电压降低到一定程度，表述数字0。</p> <h3 id="_2-3-什么是-nand-flash"><a href="#_2-3-什么是-nand-flash" class="header-anchor">#</a> 2.3 什么是 Nand Flash</h3> <p>Flash 主要分两种，Nand Flash 和 nor flash。</p> <p>NAND Flash和NOR Flash是两种常见的非易失性存储器，它们在结构、性能、应用和优缺点上有明显的区别。</p> <p><strong>以下是它们的主要区别：</strong></p> <h4 id="_2-3-1-结构和存储方式"><a href="#_2-3-1-结构和存储方式" class="header-anchor">#</a> 2.3.1 结构和存储方式</h4> <ul><li><p><strong>NAND Flash</strong>：</p> <ul><li><strong>结构</strong>：由多个单元组成的串联链，每个单元与下一个单元串联，形成一个块。</li> <li><strong>存储方式</strong>：数据以页（page）为单位进行读取和写入，以块（block）为单位进行擦除。页通常为512字节到16KB，块由多个页组成。</li> <li><strong>访问方式</strong>：串行访问，读取和写入速度较快。</li></ul></li> <li><p><strong>NOR Flash</strong>：</p> <ul><li><strong>结构</strong>：每个存储单元直接与位线相连，形成一个类似于传统内存的阵列。</li> <li><strong>存储方式</strong>：数据可以随机读取和写入，类似于RAM的访问方式。</li> <li><strong>访问方式</strong>：并行访问，具有较快的读取速度。</li></ul></li></ul> <h4 id="_2-3-2-性能"><a href="#_2-3-2-性能" class="header-anchor">#</a> 2.3.2 性能</h4> <ul><li><p><strong>NAND Flash</strong>：</p> <ul><li><strong>读取速度</strong>：相对较慢，适用于顺序读取。</li> <li><strong>写入速度</strong>：较快，适合大容量数据写入。</li> <li><strong>擦除速度</strong>：较慢，以块为单位进行擦除。</li></ul></li> <li><p><strong>NOR Flash</strong>：</p> <ul><li><strong>读取速度</strong>：较快，适合随机读取。</li> <li><strong>写入速度</strong>：较慢，特别是对单个字节的写入。</li> <li><strong>擦除速度</strong>：较慢，以扇区（sector）为单位进行擦除。</li></ul></li></ul> <h4 id="_2-3-3-存储密度和成本"><a href="#_2-3-3-存储密度和成本" class="header-anchor">#</a> 2.3.3 存储密度和成本</h4> <ul><li><p><strong>NAND Flash</strong>：</p> <ul><li><strong>存储密度</strong>：较高，适合大容量存储应用。</li> <li><strong>成本</strong>：较低，每单位存储容量的成本更低。</li></ul></li> <li><p><strong>NOR Flash</strong>：</p> <ul><li><strong>存储密度</strong>：较低，适合小容量存储应用。</li> <li><strong>成本</strong>：较高，每单位存储容量的成本更高。</li></ul></li></ul> <h4 id="_2-3-4-应用场景"><a href="#_2-3-4-应用场景" class="header-anchor">#</a> 2.3.4 应用场景</h4> <ul><li><p><strong>NAND Flash</strong>：</p> <ul><li><strong>适用场景</strong>：大容量数据存储，如固态硬盘（SSD）、USB闪存盘、存储卡和嵌入式存储设备。</li> <li><strong>优点</strong>：适合顺序存储和大容量数据存储，成本较低。</li></ul></li> <li><p><strong>NOR Flash</strong>：</p> <ul><li><strong>适用场景</strong>：需要快速随机读取的小容量存储应用，如固件存储、代码执行存储器（如微控制器的程序存储）和嵌入式系统启动代码。</li> <li><strong>优点</strong>：适合快速随机读取，读取速度较快。</li></ul></li></ul> <h4 id="_2-3-5-优缺点总结"><a href="#_2-3-5-优缺点总结" class="header-anchor">#</a> 2.3.5 优缺点总结</h4> <ul><li><p><strong>NAND Flash</strong>：</p> <ul><li><strong>优点</strong>：存储密度高、成本低、适合大容量数据存储和写入。</li> <li><strong>缺点</strong>：随机读取速度较慢，数据可靠性稍低，需要额外的ECC（错误校验码）来确保数据完整性。</li></ul></li> <li><p><strong>NOR Flash</strong>：</p> <ul><li><strong>优点</strong>：读取速度快、适合随机读取和小容量存储。</li> <li><strong>缺点</strong>：存储密度低、成本高、写入速度较慢，特别是对单个字节的写入。</li></ul></li></ul> <p>所以，一个常见的应用组合就是，用小容量的 Nor Flash 存储启动代码，比如 uboot，用大容量的 Nand Flash 做整个系统和用户数据的存储。</p> <p>而一般的嵌入式平台的启动流程也就是，系统从装有启动代码的 Nor Flash 启动后，初始化对应的硬件，包括 SDRAM等，然后将 Nand Flash 上的 Linux 内核读取到内存中，做好该做的事情后，就跳转到 SDRAM 中去执行内核了，然后内核解压（如果是压缩内核的话，否则就直接运行了）后，开始运行，在 Linux 内核启动最后，去 Nand Flash 上，挂载根文件，比如 jffs2，yaffs2 等，挂载完成，运行初始化脚本，启动 consle 交互，才允许你通过 console 和内核交互。至此完成整个系统启动过程。</p> <p>而 Nor Flash 就分别存放的是 Uboot，Nand Flash 存放的是 Linux 的内核镜像和根文件系统，以及余下的空间分成一个数据区。</p> <h3 id="_2-4-nand-flash-的详细分类"><a href="#_2-4-nand-flash-的详细分类" class="header-anchor">#</a> 2.4 Nand Flash 的详细分类</h3> <p>Nand Flash，按照硬件类型，可以分为：</p> <ol><li><strong>Bare NAND chips：</strong> 裸片。单独的 Nand Flash 芯片。</li> <li><strong>SmartMediaCards：</strong> 裸片+一层薄塑料。常用于数码相机和 MP3 播放器中。之所以称 smart，是由于其软件smart，而不是硬件本身有啥 smart 之处。</li> <li><strong>DiskOnChip：</strong> 裸片+glue logic。glue logic = 硬件ECC产生器+用于静态的 nand 芯片控制的寄存器+直接访问一小片地址窗口，那块地址中包含了引导代码的 stub 桩，其可以从 Nand Flash 中拷贝真正的引导代码。</li></ol> <h3 id="_2-5-slc-和-mlc-的实现机制"><a href="#_2-5-slc-和-mlc-的实现机制" class="header-anchor">#</a> 2.5 SLC 和 MLC 的实现机制</h3> <p>Nand Flash 按照内部存储数据单元的电压的不同层次，也就是单个内存单元中，是存储1位数据，还是多位数据，可以分为 SLC 和 MLC。</p> <h4 id="_2-5-1-slc-single-level-cell"><a href="#_2-5-1-slc-single-level-cell" class="header-anchor">#</a> 2.5.1 SLC（Single Level Cell）</h4> <p>单个存储单元，只存储一位数据，表示1或0。</p> <p>就是上面介绍的，对于数据的表示，单个存储单元中内部所存储电荷的电压，和某个特定的阈值电压 Vth，相比，如果大于此 Vth 值，就是表示1，反之，小于 Vth，就表示0。</p> <p>对于 Nand Flash 的数据的写入1，就是控制 External Gate 去充电，使得存储的电荷够多，超过阈值 Vth，就表示1了。而对于写入0，就是将其放电，电荷减少到小于 Vth，就表示0了。</p> <p>关于为何 Nand Flash 不能从0变成1，我的理解是，理论上来说，是可以实现每一位的从0变成1的，但是实际上，对于实际的物理实现，出于效率的考虑，如果对于每一个存储单元都能单独控制，即，0变成1就是，对每一个存储单元单独去充电，所需要的硬件实现就很复杂和昂贵，同时，所进行对块擦除的操作，也就无法实现之前所说的，Flash 一闪而过的速度了，也就失去了 Flash 的众多特性了。</p> <h4 id="_2-5-2-mlc-multi-level-cell"><a href="#_2-5-2-mlc-multi-level-cell" class="header-anchor">#</a> 2.5.2 MLC（Multi Level Cell）</h4> <p>与 SLC 相对应的，就是单个存储单元，可以存储多个位，比如2位，4位等。其实现机制，说起来比较简单，就是通过控制内部电荷的多少，分成多个阈值，通过控制里面的电荷多少，而达到我们所需要的存储成不同的数据。比如，假设输入电压是 Vin＝4V（实际没有这样的电压，此处只是为了举例方便），那么，可以设计出2的2次方＝4个阈值， 1/4 的 Vin＝1V，2/4 的 Vin＝2V，3/4 的 Vin＝3V，Vin＝4V，分别表示2位数据00，01，10，11，对于写入数据，就是充电，通过控制内部的电荷的多少，对应表示不同的数据。</p> <p>对于读取，则是通过对应的内部的电流（与 Vth 成反比），然后通过一系列解码电路完成读取，解析出所存储的数据。这些具体的物理实现，都是有足够精确的设备和技术，才能实现精确的数据写入和读出的。</p> <p>单个存储单元可以存储2位数据的，称作2的2次方＝4 Level Cell，而不是 2 Level Cell，关于这点，之前看 Nand flash 的数据手册（datasheet）的时候，差点搞晕了。同理，对于新出的单个存储单元可以存储4位数据的，称作 2 的 4次方＝16 Level Cell。</p> <h3 id="_2-6-如何识别-slc-还是-mlc"><a href="#_2-6-如何识别-slc-还是-mlc" class="header-anchor">#</a> 2.6 如何识别 SLC 还是 MLC</h3> <p>Nand Flash 设计中，有个命令叫做 Read ID，读取ID，意思是读取芯片的ID，就像大家的身份证一样，这里读取的 ID中，是读取好几个字节，一般最少是4个，新的芯片，支持5个甚至更多，从这些字节中，可以解析出很多相关的信息，比如此 Nand Flash 内部是几个芯片（chip）所组成的，每个 chip 包含了几片（Plane），每一片中的页大小，块大小，等等。在这些信息中，其中有一个，就是识别此 flash 是 SLC 还是 MLC。下面这个就是最常见的 Nand Flash 的 datasheet 中所规定的，第3个字节，3rd byte，所表示的信息，其中就有 SLC/MLC 的识别信息：</p> <p><strong>表 1.1. Nand Flash第3个ID的含义</strong></p> <table><thead><tr><th></th> <th style="text-align:left;">Description</th> <th>I/O7</th> <th>I/O6</th> <th>I/O5 I/O4</th> <th>I/O3 I/O2</th> <th style="text-align:left;">I/O2 I/O1</th></tr></thead> <tbody><tr><td>内部芯片数 (Internal Chip Number)</td> <td style="text-align:left;">1<br>2<br>4<br>8</td> <td></td> <td></td> <td></td> <td></td> <td style="text-align:left;">0   0<br>0   1<br>1   0<br>1   1</td></tr> <tr><td>单元类型 (Cell Type)</td> <td style="text-align:left;">2 Level Cell<br>4 Level Cell<br>4 Level Cell<br>8 Level Cell</td> <td></td> <td></td> <td></td> <td>0   0<br>0   1<br>1   0<br>1   1</td> <td style="text-align:left;"></td></tr> <tr><td>同时编程页数<br> (Number of Simultaneously Programmed Pages)</td> <td style="text-align:left;">1<br>2<br>4<br>8</td> <td></td> <td></td> <td>0   0<br>0   1<br>1   0<br>1   1</td> <td></td> <td style="text-align:left;"></td></tr> <tr><td>多芯片之间的交错编程<br> (Interleave Program Between multiple chips)</td> <td style="text-align:left;">Not Support<br>Support</td> <td></td> <td>0<br>1</td> <td></td> <td></td> <td style="text-align:left;"></td></tr> <tr><td>缓存程序 (Cache Program)</td> <td style="text-align:left;">Not Support<br>Support</td> <td>0<br>1</td> <td></td> <td></td> <td></td> <td style="text-align:left;"></td></tr></tbody></table> <p><strong>字节的位定义</strong>：</p> <ul><li>表格顶部的列 I/O7 到 I/O0 分别代表第 3 个字节中的 8 个比特（从最高位到最低位）。</li></ul> <p><strong>内部芯片数 (Internal Chip Number)</strong>：</p> <ul><li>描述了 NAND Flash 中的内部芯片数量。</li> <li>比特组合：
<ul><li><code>00</code> (I/O1 I/O0) 代表 1 个芯片。</li> <li><code>01</code> (I/O1 I/O0) 代表 2 个芯片。</li> <li><code>10</code> (I/O1 I/O0) 代表 4 个芯片。</li> <li><code>11</code> (I/O1 I/O0) 代表 8 个芯片。</li></ul></li></ul> <p><strong>单元类型 (Cell Type)</strong>：</p> <ul><li>描述了 NAND Flash 的单元类型，通常指的是 SLC（Single-Level Cell）或 MLC（Multi-Level Cell）。</li> <li>比特组合：
<ul><li><code>00</code> (I/O3 I/O2) 代表 2 Level Cell（通常是 SLC）。</li> <li><code>01</code> (I/O3 I/O2) 代表 4 Level Cell。</li> <li><code>10</code> (I/O3 I/O2) 代表 4 Level Cell。</li> <li><code>11</code> (I/O3 I/O2) 代表 8 Level Cell（通常是 MLC 或更高级的单元）。</li></ul></li></ul> <p><strong>同时编程页数 (Number of Simultaneously Programmed Pages)</strong>：</p> <ul><li>描述了可以同时编程的页数。</li> <li>比特组合：
<ul><li><code>00</code> (I/O5 I/O4) 代表 1 页。</li> <li><code>01</code> (I/O5 I/O4) 代表 2 页。</li> <li><code>10</code> (I/O5 I/O4) 代表 4 页。</li> <li><code>11</code> (I/O5 I/O4) 代表 8 页。</li></ul></li></ul> <p><strong>多芯片之间的交错编程 (Interleave Program Between multiple chips)</strong>：</p> <ul><li>描述了是否支持多芯片之间的交错编程。</li> <li>比特：
<ul><li><code>0</code> (I/O6) 代表不支持。</li> <li><code>1</code> (I/O6) 代表支持。</li></ul></li></ul> <p><strong>缓存程序 (Cache Program)</strong>：</p> <ul><li>描述了是否支持缓存编程。</li> <li>比特：
<ul><li><code>0</code> (I/O7) 代表不支持。</li> <li><code>1</code> (I/O7) 代表支持。</li></ul></li></ul> <blockquote><h3 id="例子"><a href="#例子" class="header-anchor">#</a> 例子：</h3> <p>如果第 3 个字节的值是 <code>10101100</code>，则对应的信息如下：</p> <ul><li>I/O7 = 1, 支持缓存程序。</li> <li>I/O6 = 0, 不支持多芯片之间的交错编程。</li> <li>I/O5 I/O4 = 10, 同时编程页数为 4 页。</li> <li>I/O3 I/O2 = 10, 单元类型为 4 Level Cell。</li> <li>I/O1 I/O0 = 00, 内部芯片数量为 1 个。</li></ul></blockquote> <h3 id="_2-7-nand-flash-数据存储单元的整体架构"><a href="#_2-7-nand-flash-数据存储单元的整体架构" class="header-anchor">#</a> 2.7 Nand Flash 数据存储单元的整体架构</h3> <p>简单说就是，常见的 Nand Flash，内部只有一个 chip，每个 chip 只有一个 plane。</p> <p>而有些复杂的，容量更大的 Nand Flash，内部有多个 chip，每个 chip 有多个 plane。这类的 Nand Flash，往往也有更加高级的功能，比如下面要介绍的 Multi Plane Program 和 Interleave Page Program 等。</p> <p>概念上，由大到小来说，就是：Nand Flash ⇒ Chip ⇒ Plane ⇒ Block ⇒ Page ⇒ oob</p> <p>用图表来表示，更加易懂：</p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.3yecdhywpm.webp" loading="lazy" class="lazy"></p> <p>比如，型号为 K9K8G08U0A 这块 Nand Flash（有时候也被称为此块 chip 芯片），其内部有两个 K9F4G08U0A 的chip，chip#1 和 chip#2，每个 K9F4G08U0A 的 chip 包含了2个 Plane，每个 Plane 是 2Gb bit，所以 K9F4G08U0A 的大小是 2Gb×2 = 4Gb = 512MB，因此，K9K8G08U0A 内部有2个 K9F4G08U0A，或者说4个 Plane，总大小是×256MB＝1GB。</p> <blockquote><p>==<strong>注意：4Gb ≠ 4GB，4Gb = 4G bit，4GB = 4G Byte，所以 4G bit = 4 ×1024 ÷ 8 = 512 MB，（1Byte = 8 bit）</strong>==</p></blockquote> <p>用公式表示如下：</p> <p><strong>K9K8G08U0A 的物理结构所组成的总容量</strong></p> <p>K9K8G08U0A(这块 Nand Flash)</p> <p>= 2 × K9F4G08U0A(K9F4G08U0A 是 chip，1 K9F4G08U0A = 2 Plane)</p> <p>= 2 × 2个Plane</p> <p>= 4 Plane(1 Plane = 2048 Block)</p> <p>= 4 × 2048个Block(1 Block = 64 Page)</p> <p>= 4 × 2048 × 64Page(1 Page = 2KB)</p> <p>= 4 × 2048 × 64 × 2KB</p> <p>= 4 × 2048 × 128KB(1 Block = 128KB)</p> <p>= 4 × 256MB(1 Plane = 2Gb = 256MB)</p> <p>= 2 × 512MB(1 K9F4G08U0A = 4Gb = 512MB)</p> <p>= 1GB(1 K9K8G08U0A = 1GB)</p> <p>而型号是 K9WAG08U1A 的 Nand Flash，内部包含了2个 K9K8G08U0A，所以，总容量是 K9K8G08U0A 的两倍＝1GB×2＝2GB，类似地 K9NBG08U5A，内部包含了4个 K9K8G08U0A，总大小就是 4×1GB＝4GB。</p> <blockquote><p><strong>通常只关心 Nand 的总大小</strong></p> <p>上面所说的 block，page 等 Nand Flash 的物理上的组织结构，是在 chip 的基础上来说的，但是软件编程的时候，除非你要用到 Multi Plane Program 和 Interleave Page Program 等，一般很少区分内部有几个 chip 以及每个 chip 有几个 plane，而最关心的只是 Nand Flash 的总体容量 size 有多大，比如是 1GB 还是 2GB 等等。</p></blockquote> <h3 id="_2-8-nand-flash的物理存储单元的阵列组织结构"><a href="#_2-8-nand-flash的物理存储单元的阵列组织结构" class="header-anchor">#</a> 2.8 Nand Flash的物理存储单元的阵列组织结构</h3> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.86tjoba6s1.webp" loading="lazy" class="lazy"></p> <p>上图是 K9K8G08U0A 的 datasheet 中的描述。</p> <h4 id="_2-8-1-block-块"><a href="#_2-8-1-block-块" class="header-anchor">#</a> 2.8.1 Block 块</h4> <p>一个 Nand Flash（的chip，芯片）由很多个块（Block）组成，块的大小一般是128KB，256KB，512KB，此处是128 KB。其他的小于 128KB 的，比如 64KB，一般都是下面将要介绍到的 small block 的 Nand Flash。</p> <p>块 Block，是 Nand Flash 的擦除操作的基本/最小单位。</p> <h4 id="_2-8-2-page-页"><a href="#_2-8-2-page-页" class="header-anchor">#</a> 2.8.2 Page 页</h4> <p>每个块里面又包含了很多页（page）。每个页的大小，对于现在常见的 Nand Flash 多数是2KB，最新的 Nand Flash 的是4KB、8KB等，这类的页大小大于2KB的 Nand Flash，被称作 big block 的 Nand Flash，对应的发读写命令地址，一共5个周期(cycle)，而老的 Nand Flash，页大小是256B，512B，这类的 Nand Flash 被称作 small block，地址周期只有4个。</p> <p>页 Page，是读写操作的基本单位。</p> <p>不过，也有例外的是，有些 Nand Flash 支持 subpage（1/2页或1/4页）子页的读写操作，不过一般很少见。</p> <h4 id="_2-8-3-oob-redundant-area-spare-area"><a href="#_2-8-3-oob-redundant-area-spare-area" class="header-anchor">#</a> 2.8.3  oob / Redundant Area / Spare Area</h4> <p>每一个页，对应还有一块区域，叫做空闲区域（spare area）/冗余区域（redundant area），而 Linux 系统中，一般叫做 OOB（Out Of Band），这个区域，是最初基于 Nand Flash 的硬件特性：数据在读写时候相对容易错误，所以为了保证数据的正确性，必须要有对应的检测和纠错机制，此机制被叫做 EDC(Error Detection Code)/ECC（Error Code Correction, 或者 Error Checking and Correcting），所以设计了多余的区域，用于放置数据的校验值。</p> <p>Oob 的读写操作，一般是随着页的操作一起完成的，即读写页的时候，对应地就读写了 oob。</p> <p>关于 oob 具体用途，总结起来有：</p> <ol><li>标记是否是坏快</li> <li>存储 ECC 数据</li> <li>存储一些和文件系统相关的数据。如 jffs2 就会用到这些空间存储一些特定信息，而 yaffs2 文件系统，会在 oob 中，存放很多和自己文件系统相关的信息。</li></ol> <h3 id="_2-9-flash-名称的由来"><a href="#_2-9-flash-名称的由来" class="header-anchor">#</a> 2.9 Flash 名称的由来</h3> <p>Flash 的擦除操作是以 block 块为单位的，与此相对应的是其他很多存储设备，是以 bit 位为最小读取/写入的单位，Flash 是一次性地擦除整个块：在发送一个擦除命令后，一次性地将一个 block，常见的块的大小是 128KB/256KB，全部擦除为1，也就是里面的内容全部都是 0xFF 了，由于是一下子就擦除了，相对来说，擦除用的时间很短，可以用一闪而过来形容，所以，叫做 Flash Memory。所以一般将 Flash 翻译为 （快速）闪存。</p> <h3 id="_2-10-flash-相对于普通设备的特殊性"><a href="#_2-10-flash-相对于普通设备的特殊性" class="header-anchor">#</a> 2.10 Flash 相对于普通设备的特殊性</h3> <p>一般存储设备，比如硬盘或内存，读取和写入都是以位（bit）为单位，读取一个 bit 的值，将某个值写入对应的地址的位，都是可以按位操作的。</p> <p>但是 Flash 由于物理特性，使得内部存储的数据，只能从1变成0，这点，这点可以从前面的内部实现机制了解到，对于最初始值，都是1，所以是 0xFFFFFFFF，而数据的写入，即是将对应的变成0，而将数据的擦出掉，就是统一地，以 block为单位，全部一起充电，所有位，都变成初始的1，而不是像普通存储设备那样，每一个位去擦除为0。而数据的写入，就是电荷放电的过程，代表的数据也从1变为了0。</p> <p>所以，总结一下 Flash 的特殊性如下：</p> <table><thead><tr><th></th> <th><strong>普通设备(硬盘/内存等)</strong></th> <th><strong>Flash</strong></th></tr></thead> <tbody><tr><td>读取/写入的叫法</td> <td>读取/写入</td> <td>读取/编程(Program)</td></tr> <tr><td>读取/写入的最小单位</td> <td>Bit/位</td> <td>Page/页</td></tr> <tr><td>擦除(Erase)操作的最小单位</td> <td>Bit/位</td> <td>Block/块</td></tr> <tr><td>擦除操作的含义</td> <td>将数据删除/全部写入0</td> <td>将整个块都擦除成全是1，也就是里面的数据都是0xFF</td></tr> <tr><td>对于写操作</td> <td>直接写即可</td> <td>在写数据之前，要先擦除，然后再写</td></tr></tbody></table> <blockquote><ol><li>之所以将写操作叫做编程，是因为 flash 是从之前的 EPROM、EEPROM 等继承发展而来，而之前的 EEPROM，往里面写入数据，就叫做编程 Program，之所以这么称呼，是因为其对数据的写入，是需要用电去擦除/写入的，所以叫做编程。</li> <li>对于目前常见的页大小是 2K/4K 的 Nand Flash，其块的大小有128KB/256KB/512KB 等。而对于 Nor Flash，常见的块大小有64K/32K 等。</li> <li>在写数据之前，要先擦除，内部就都变成 0xFF 了，然后才能写入数据，也就是将对应的位由1变成0。</li></ol></blockquote> <h3 id="_2-11-nand-flash-的位反转特性"><a href="#_2-11-nand-flash-的位反转特性" class="header-anchor">#</a> 2.11 Nand Flash 的位反转特性</h3> <p>Nand Flash 的位反转，也叫做位翻转，对应的英文表达有：Bit Flip = Bit Flipping = Bit-Flip = Bit twiddling。</p> <p>Nand Flash 由于本身硬件的内在特性，会导致（极其）偶尔的出现位反转的现象。</p> <p>所谓的位反转，bit flip，指的是原先 Nand Flash 中的某个位，变化了，即要么从1变成0了，要么从0变成1了。</p> <h3 id="_2-12-nand-flash-位反转的原因"><a href="#_2-12-nand-flash-位反转的原因" class="header-anchor">#</a> 2.12 Nand Flash 位反转的原因</h3> <p>Nand Flash 的位反转现象，主要是由以下一些原因/效应所导致：</p> <ol><li><p>漂移效应（Drifting Effects）</p> <p>漂移效应指的是，Nand Flash 中 cell 的电压值，慢慢地变了，变的和原始值不一样了。</p></li> <li><p>编程干扰所产生的错误（Program-Disturb Errors）</p> <p>此现象有时候也叫做，过度编程效应（over-program effect）。</p> <p>对于某个页面的编程操作，即写操作，引起非相关的其他的页面的某个位跳变了。</p></li> <li><p>读操作干扰产生的错误（Read-Disturb Errors）</p> <p>此效应是，对一个页进行数据读取操作，却使得对应的某个位的数据，产生了永久性的变化，即 Nand Flash 上的该位的值变了。</p></li></ol> <h3 id="_2-13-nand-flash-位反转的影响"><a href="#_2-13-nand-flash-位反转的影响" class="header-anchor">#</a> 2.13 Nand Flash 位反转的影响</h3> <p>位反转，说白了，就是读取数据的时候，数据出错了。</p> <p>因此，如果你读取的数据正好是属于某个重要的文件中的数据，比如系统的配置文件等，那么此时错了一位，都会导致系统出现异常，问题相对会很严重。</p> <p>而如果此数据属于音视频流中的数据，那么此时即使错了一位，对整个音视频的播放产生的影响也很小，所以问题也不大。</p> <h3 id="_2-14-nand-flash-位反转的类型和解决办法"><a href="#_2-14-nand-flash-位反转的类型和解决办法" class="header-anchor">#</a> 2.14 Nand Flash 位反转的类型和解决办法</h3> <p>对应的位反转的类型，有两种：</p> <ol><li>一种是 nand flash 物理上的数据存储的单元上的数据，是正确的，只是在读取此数据出来的数据中的某位，发生变化，出现了位反转，即读取出来的数据中，某位错了，本来是0变成1，或者本来是1变成0了。此处可以成为软件上位反转。此数据位的错误，当然可以通过一定的校验算法检测并纠正。</li> <li>另外一种，就是 nand flash 中的物理存储单元中，对应的某个位，物理上发生了变化，原来是1的，变成了0，或原来是0的，变成了1，发生了物理上的位的数据变化。此处可以成为硬件上的位反转。此错误，由于是物理上发生的，虽然读取出来的数据的错误，可以通过软件或硬件去检测并纠正过来，但是物理上真正发生的位的变化，则没办法改变了。不过个人理解，好像也是可以通过擦除 Erase 整个数据块 Block 的方式去擦除此错误，不过在之后的 Nand Flash 的使用过程中，估计此位还是很可能继续发生同样的硬件的位反转的错误。</li></ol> <p>以上两种类型的位反转，其实对于从 Nand Flash 读取出来的数据来说，解决其中的错误的位的方法，都是一样的，即通过一定的校验算法，常称为 ECC，去检测出来，或检测并纠正错误。</p> <p>如果只是单独检测错误，那么如果发现数据有误，那么再重新读取一次即可。</p> <p>实际中更多的做法是，ECC 校验发现有错误，会有对应的算法去找出哪位错误并且纠正过来。</p> <p>其中对错误的检测和纠正，具体的实现方式，有软件算法，也有硬件实现，即硬件 Nand Flash 的控制器 controller 本身包含对应的硬件模块以实现数据的校验和纠错的。</p> <h3 id="_2-15-nand-flash-引脚-pin-的说明"><a href="#_2-15-nand-flash-引脚-pin-的说明" class="header-anchor">#</a> 2.15 Nand Flash 引脚(Pin)的说明</h3> <table><thead><tr><th style="text-align:center;"><strong>引脚名称</strong></th> <th><strong>引脚功能</strong></th></tr></thead> <tbody><tr><td style="text-align:center;">I/O0 ~ I/O7</td> <td>用于输入地址/数据/命令，输出数据</td></tr> <tr><td style="text-align:center;">CLE</td> <td>Command Latch Enable，命令锁存使能，在输入命令之前，要先在模式寄存器中，设置CLE使能</td></tr> <tr><td style="text-align:center;">ALE</td> <td>Address Latch Enable，地址锁存使能，在输入地址之前，要先在模式寄存器中，设置ALE使能</td></tr> <tr><td style="text-align:center;">CE#</td> <td>Chip Enable，芯片使能，在操作Nand Flash之前，要先选中此芯片，才能操作</td></tr> <tr><td style="text-align:center;">RE#</td> <td>Read Enable，读使能，在读取数据之前，要先使 CE＃有效。</td></tr> <tr><td style="text-align:center;">WE#</td> <td>Write Enable，写使能, 在写取数据之前，要先使 WE＃有效</td></tr> <tr><td style="text-align:center;">WP#</td> <td>Write Protect，写保护</td></tr> <tr><td style="text-align:center;">R/B#</td> <td>Ready/Busy Output,就绪/忙,主要用于在发送完编程/擦除命令后,检测这些操作是否完成,忙,表示编程/擦除操作仍在进行中,就绪表示操作完成</td></tr> <tr><td style="text-align:center;">Vcc</td> <td>Power，电源</td></tr> <tr><td style="text-align:center;">Vss</td> <td>Ground，接地</td></tr> <tr><td style="text-align:center;">N.C</td> <td>Non-Connection,未定义，未连接</td></tr></tbody></table> <blockquote><p>==<strong>数据手册中的#表示低电平</strong>==</p> <p>在数据手册中，你常会看到，对于一个引脚定义，有些字母上面带一横杠的，那是说明此引脚/信号是低电平有效，比如你上面看到的RE头上有个横线，就是说明，此RE是低电平有效，此外，为了书写方便，在字母后面加“＃”，也是表示低电平有效，比如我上面写的CE＃；如果字母头上啥都没有，就是默认的高电平有效，比如上面的CLE，就是高电平有效。</p> <ul><li>CLE 上升沿，命令被锁存</li> <li>ALE 上升沿，地址被锁存</li> <li>Ｒ/Ｂ 处于低电平时，表示有编程、擦除或随机读操作正在进行。当 Ｒ/Ｂ 处于低电平时，主控无法对 FLASH 进行读写操作，只有当 Ｒ/Ｂ 处于高电平时，主控才能对 FLASH 进行读写操作。</li> <li>CE：片选，大致来说，一个 DIE 有一个 CE，多 CE 的 单片闪存，就是多管芯的闪存，相当于一片闪存那么大的地方，装下了不止一片闪存，特别是 BGA 的板子，能实现单片双通道，就是这个原理。</li></ul></blockquote> <h3 id="_2-16-为何需要-ale-和-cle"><a href="#_2-16-为何需要-ale-和-cle" class="header-anchor">#</a> 2.16 为何需要 ALE 和 CLE</h3> <p>硬件上，有了电源的 Vcc 和接地的 Vss 等引脚，很好理解，但是为何还要有 ALE 和 CLE 这样的引脚，为何设计这么多的命令,把整个系统搞这么复杂，关于这点，最后终于想明白了：</p> <p>设计命令锁存使能(Command Latch Enable, CLE) 和 地址锁存使能(Address Latch Enable，ALE)，那是因为，Nand Flash 就 8 个 I/O，而且是复用的，也就是，可以传数据，也可以传地址，也可以传命令，为了区分你当前传入的到底是啥，所以，先要用发一个 CLE（或ALE）命令，告诉 Nand Flash 的控制器一声，我下面要传的是命令（或地址），这样，里面才能根据传入的内容，进行对应的动作。否则，Nand Flash 内部,怎么知道你传入的是数据,还是地址,还是命令,也就无法实现正确的操作了。</p> <h3 id="_2-17-nand-flash-只有-8个-i-o-引脚的好处"><a href="#_2-17-nand-flash-只有-8个-i-o-引脚的好处" class="header-anchor">#</a> 2.17 Nand Flash 只有 8个 I/O 引脚的好处</h3> <p>在 Nand Flash 的硬件设计中，你会发现很多个引脚。关于硬件上为何设计这样的引脚，而不是直接像其他存储设备，比如普通的 RAM，直接是一对数据线引出来，多么方便和好理解啊。</p> <p><strong>关于这样设计的好处：</strong></p> <ol><li>减少外围连线
<ul><li>相对于并口 (Parellel) 的 Nor Flash 的 48 或 52 个引脚来说，的确是大大减小了引脚数目，这样封装后的芯片体积，就小很多。现在芯片在向体积更小，功能更强，功耗更低发展，减小芯片体积，就是很大的优势。同时，减少芯片接口，也意味着使用此芯片的相关的外围电路会更简化，避免了繁琐的硬件连线。</li></ul></li> <li>提高系统的可扩展性
<ul><li>因为没有像其他设备一样用物理大小对应的完全数目的 addr 引脚，在芯片内部换了芯片的大小等的改动，对于用全部的地址 addr 的引脚，那么就会引起这些引脚数目的增加，比如容量扩大一倍，地址空间/寻址空间扩大一倍，所以，地址线数目/addr 引脚数目，就要多加一个，而对于统一用8个 I/O 的引脚的 Nand Flash，由于对外提供的都是统一的8个引脚，内部的芯片大小的变化或者其他的变化，对于外部使用者(比如编写 Nand Flash 驱动的人)来说，不需要关心，只是保证新的芯片，还是遵循同样的接口，同样的时序，同样的命令，就可以了。这样就提高了系统的扩展性。</li></ul></li></ol> <p>说白了，对于旧的Nand Flash所实现的驱动，这些软件工作，在换新的硬件的Nand Flash的情况下，仍然可以工作，或者是通过极少的修改，就同样可以工作，使得软硬件兼容性大大提高。</p> <h3 id="_2-18-nand-flash-的一些典型-typical-的特性"><a href="#_2-18-nand-flash-的一些典型-typical-的特性" class="header-anchor">#</a> 2.18 Nand Flash 的一些典型 (typical) 的特性</h3> <ol><li><p>页擦除时间是200us，有些慢的有800us</p></li> <li><p>块擦除时间是1.5ms</p></li> <li><p>页数据读取到数据寄存器的时间一般是20us</p></li> <li><p>串行访问（Serial access）读取一个数据的时间是25ns，而一些旧的 Nand Flash 是30ns，甚至是50ns</p></li> <li><p>输入输出端口是地址和数据以及命令一起multiplex复用的</p></li> <li><p>Nand Flash 的编程/擦除的寿命：即，最多允许的擦除的次数</p> <p>以前老的 Nand Flash，编程/擦除时间比较短，比如 K9G8G08U0M，才5K次，而后来的多数也只有10K=1万次，而现在很多新的 Nand Flash，技术提高了，比如，Micron 的 MT29F1GxxABB，Numonyx 的 NAND04G-B2D/NAND08G-BxC，都可以达到100K，也就是10万次的编程/擦除，达到和接近于之前常见的 Nor Flash，几乎是同样的使用寿命了。</p></li> <li><p>封装形式</p> <p>48引脚的 TSOP1 封装 或 52引脚的 ULGA 封装</p></li></ol> <h3 id="_2-19-nand-flash-控制器与-nand-flash-芯片"><a href="#_2-19-nand-flash-控制器与-nand-flash-芯片" class="header-anchor">#</a> 2.19 Nand Flash 控制器与 Nand Flash 芯片</h3> <p>关于 Nand Flash 的控制器 Controller 和 Nand Flash 芯片 chip 之间的关系，觉得有必要解释一下：</p> <p>首先，我们要知道的是，我们写驱动，是写 Nand Flash 控制器的驱动，而不是 Nand Flash 芯片的驱动，因为独立的 Nand Flash 芯片，一般来说，是很少直接拿来用的，多数都是硬件上有对应的硬件的 Nand Flash 的控制器，去操作和控制 Nand Flash，包括提供时钟信号，提供硬件 ECC 校验等等功能，我们所写的驱动软件，是去操作 Nand Flash 的控制器.</p> <p>然后由控制器去操作 Nand Flash 芯片，实现我们所要的功能。</p> <h3 id="_2-20-nand-flash-中的特殊硬件结构"><a href="#_2-20-nand-flash-中的特殊硬件结构" class="header-anchor">#</a> 2.20 Nand Flash 中的特殊硬件结构</h3> <p>由于 Nand Flash 相对其他常见设备来说，比较特殊，所以，特殊的设备，就有特殊的设计，就对应某些特殊的硬件特性，就有必要解释解释：</p> <p>页寄存器（Page Register）：</p> <p>由于 Nand Flash 读取和编程操作来说，一般最小单位是页，所以 Nand Flash 在硬件设计时候，考虑到这一特性，对于每一片（Plane），都有一个对应的区域专门用于存放，将要写入到物理存储单元中去的或者刚从存储单元中读取出来的，一页的数据，这个数据缓存区，本质上就是一个缓存 buffer，但是只是此处 datasheet 里面把其叫做页寄存器 page register 而已，实际将其理解为页缓存，更贴切原意。</p> <p>若是不了解内部结构，容易产生误解，以为内存里面的数据，通过 Nand Flash 的 FIFO，写入到 Nand Flash 里面去，就以为立刻实现了实际数据写入到物理存储单元中了，而实际上只是写到了这个页缓存中，只有当你再发送了对应的编程第二阶段的确认命令，即 0x10，之后，实际的编程动作才开始，才开始把页缓存中的数据，一点点写到物理存储单元中去。</p> <p>所以，简单总结一下就是，对于数据的流向，实际是经过了如下步骤：</p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.2obf8fz2uo.webp" loading="lazy" class="lazy"></p> <h3 id="_2-21-nand-flash-中的坏块-bad-block"><a href="#_2-21-nand-flash-中的坏块-bad-block" class="header-anchor">#</a> 2.21 Nand Flash 中的坏块 (Bad Block)</h3> <p>Nand Flash 中，一个块中含有1个或多个位是坏的，就称为其为坏块 Bad Block。</p> <p>坏块的稳定性是无法保证的，也就是说，不能保证你写入的数据是对的，或者写入对了，读出来也不一定对的。与此对应的正常的块，肯定是写入读出都是正常的。</p> <h4 id="_2-21-1-坏块的分类"><a href="#_2-21-1-坏块的分类" class="header-anchor">#</a> 2.21.1 坏块的分类</h4> <p>坏块有两种：</p> <ol><li><p>出厂时就有存在的坏块</p> <p>一种是出厂的时候，也就是，你买到的新的，还没用过的 Nand Flash，就可以包含了坏块。此类出厂时就有的坏块，被称作 factory (masked) bad block 或 initial bad/invalid block，在出厂之前，就会做对应的标记，标为坏块。</p></li> <li><p>使用过程中产生的坏块</p> <p>第二类叫做在使用过程中产生的，由于使用过程时间长了，在擦块除的时候，出错了，说明此块坏了，也要在程序运行过程中，发现，并且标记成坏块的。具体标记的位置，和上面一样。这类块叫做 worn-out bad block。即用坏了的块。</p></li></ol> <h4 id="_2-21-2-坏块的标记"><a href="#_2-21-2-坏块的标记" class="header-anchor">#</a> 2.21.2 坏块的标记</h4> <p>具体标记的地方是，对于现在常见的页大小为 2K 的 Nand Flash，是块中第一个页的 oob 起始位置（关于什么是页和 oob，下面会有详细解释）的第1个字节（旧的小页面，pagesize 是 512B 甚至 256B 的 Nand Flash，坏块标记是第6个字节），如果不是 0xFF，就说明是坏块。相对应的是，所有正常的块，好的块，里面所有数据都是 0xFF 的。</p> <p>不过，对于现在新出的有些 Nand Flash，很多标记方式，有些变化，有的变成该坏块的第一个页或者第二个页，也有的是，倒数最后一个或倒数第二个页，用于标记坏块的。</p> <p>具体的信息，请参考对应的 Nand Flash 的数据手册，其中会有说明。</p> <p>对于坏块的标记，本质上，也只是对应的 flash 上的某些字节的数据是非 0xFF 而已，所以，只要是数据，就是可以读取和写入的。也就意味着，可以写入其他值，也就把这个坏块标记信息破坏了。对于出厂时的坏块，一般是不建议将标记好的信息擦除掉的。</p> <p>uboot 中有个命令是</p> <div class="language- line-numbers-mode"><pre class="language-text"><code>nand scrub
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>就可以将块中所有的内容都擦除了，包括坏块标记，不论是出厂时的，还是后来使用过程中出现而新标记的。一般来说，不建议用这个。</p> <p>不过，在实际的驱动编程开发过程中，为了方便起见，我倒是经常用，其实也没啥大碍，呵呵。不过呢，其实最好的做法是，用</p> <div class="language- line-numbers-mode"><pre class="language-text"><code>nand erase
</code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>只擦除好的块，对于已经标记坏块的块，不要轻易擦除掉，否则就很难区分哪些是出厂时就坏的，哪些是后来使用过程中用坏的了。</p> <h4 id="_2-21-3-坏块的管理"><a href="#_2-21-3-坏块的管理" class="header-anchor">#</a> 2.21.3 坏块的管理</h4> <p>对于坏块的管理，在 Linux 系统中，叫做坏块管理（BBM，Bad Block Management），对应的会有一个表去记录好块，坏块的信息，以及坏块是出厂就有的，还是后来使用产生的，这个表叫做坏块表（BBT，Bad Block Table）。在Linux 内核 MTD 架构下的 Nand Flash 驱动，和 Uboot 中 Nand Flash 驱动中，在加载完驱动之后，如果你没有加入参数主动要求跳过坏块扫描的话，那么都会去主动扫描坏块，建立必要的 BBT 的，以备后面坏块管理所使用。</p> <h4 id="_2-21-4-坏块的比例"><a href="#_2-21-4-坏块的比例" class="header-anchor">#</a> 2.21.4 坏块的比例</h4> <p>关于好块和坏块，Nand Flash 在出厂的时候，会做出保证：</p> <ol><li>关于好的，可以使用的块的数目达到一定的数目，比如三星的 K9G8G08U0M，整个 flash 一共有4096个块，出厂的时候，保证好的块至少大于3996个，也就是意思是，你新买到这个型号的 Nand Flash，最坏的可能， 有3096－3996＝100个坏块。不过，事实上，现在出厂时的坏块，比较少，绝大多数，都是使用时间长了，在使用过程中出现的。</li> <li>保证第一个块是好的，并且一般相对来说比较耐用。做此保证的主要原因是，很多 Nand Flash 坏块管理方法中，就是将第一个块，用来存储上面提到的 BBT，否则，都是出错几率一样的块，那么也就不太好管理了，连放 BBT 的地方，都不好找了，^_^。</li></ol> <p>一般来说，不同型号的 Nand Flash 的数据手册中，也会提到，自己的这个 Nand Flash，最多允许多少个坏块。就比如上面提到的，三星的 K9G8G08U0M，最多有100个坏块。</p> <h3 id="_2-22-nand-flash-中页的访问顺序"><a href="#_2-22-nand-flash-中页的访问顺序" class="header-anchor">#</a> 2.22 Nand Flash 中页的访问顺序</h3> <p>在一个块内，对每一个页进行编程的话，必须是顺序的，而不能是随机的。比如，一个块中有128个页，那么你只能先对page0 编程，再对 page1 编程，而不能随机的，比如先对 page3，再 page1，page2，page0，page4。</p> <p>关于此处对于只能顺序给页编程的说法，只是翻译自 datasheet，但是实际情况却发现是，程序中没有按照此逻辑处理，可以任意对某 Block 内的 Page 去做 Program 的动作，而不必是顺序的。但是 datasheet 为何如此解释，原因未知，有待知情者给解释一下。</p> <h3 id="_2-23-常见的-nand-flash-的操作"><a href="#_2-23-常见的-nand-flash-的操作" class="header-anchor">#</a> 2.23 常见的 Nand Flash 的操作</h3> <p>要实现对 Nand Flash 的操作，比如读取一页的数据，写入一页的数据等，都要发送对应的命令，而且要符合硬件的规定，如图：<strong>Nand Flash K9K8G08U0A的命令集合</strong></p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.99t8zili5g.webp" loading="lazy" class="lazy"></p> <p>从上图可以看到，如果要实现读一个页的数据，就要发送Read的命令，而且是分两个周期（Cycle），即分两次发送对应的命令，第一次是0x00h,第二次是0x30h，而两次命令中间，需要发送对应的你所要读取的页的地址.</p> <p>对应地，其他常见的一些操作，比如写一个页的数据(Page Program)，就是先发送0x80h,然后发生要写入的地址，再发送0x10h。</p> <blockquote><p>注意</p> <p>对于不同厂家的不同型号的 Nand Flash 的基本操作，即读页数据 Read Page，写页数据（对页进行编程）Page Program，擦除整个块的数据 Erase Block 等操作，所用的命令都是一样的，但是针对一些 Nand Flash 的高级的一些特性，比如交错页编程（Interleave Page Program），多片同时编程 (Simultaneously Program Multi Plane)等，所用的命令，未必一样，不过对于同一厂家的 Nand Flash 的芯片，那一般来说，都是统一的。</p></blockquote> <h4 id="_2-23-1-页编程-page-program-注意事项"><a href="#_2-23-1-页编程-page-program-注意事项" class="header-anchor">#</a> 2.23.1 页编程（Page Program）注意事项</h4> <p>Nand flash 的写操作叫做编程 Program，编程，一般情况下，是以页为单位的。</p> <p>有的 Nand Flash，比如 K9K8G08U0A，支持部分页编程（Partial Page Program），但是有一些限制：在同一个页内的，连续的部分页的编程，不能超过4次。</p> <p>一般情况下，都是以页为单位进行编程操作的，很少使用到部分页编程。</p> <p>关于这个部分页编程，本来是一个页的写操作，却用两个命令或更多的命令去实现，看起来是操作多余，效率不高，但是实际上，有其特殊考虑：</p> <p>至少对于块擦除来说，开始的命令 0x60 是擦除设置命令(erase setup comman)，然后传入要擦除的块地址，然后再传入擦除确认命令（erase confirm command）0xD0，以开始擦除的操作。</p> <p>这种完成单个操作要分两步发送命令的设计，即先开始设置，再最后确认的命令方式，是为了避免由于外部由于无意的/未预料而产生的噪音，比如，由于某种噪音，而产生了 0x60 命令，此时，即使被 Nand Flash 误认为是擦除操作，但是没有之后的确认操作 0xD0，Nand Flash 就不会去擦除数据，这样使得数据更安全，不会由于噪音而误操作。</p> <h4 id="_2-23-2-读-read-操作过程详解"><a href="#_2-23-2-读-read-操作过程详解" class="header-anchor">#</a> 2.23.2 读（Read）操作过程详解</h4> <p>下面以最简单的 read 操作为例，解释如何理解时序图，以及将时序图中的要求，转化为代码。</p> <p>解释时序图之前，让我们先要搞清楚，我们要做的事情：</p> <p>从 Nand Flash 的某个页 Page 里面，读取我们要的数据。</p> <p>要实现此功能，会涉及到几部分的知识，即使我们不太懂 Nand Flash 的细节，但是通过前面的基本知识的介绍，那么以我们的常识，至少很容易想到的就是，需要用到哪些命令，怎么发这些命令，怎么计算所需要的地址，怎么读取我们要的数据等等。</p> <p>下面就一步步的解释，需要做什么，以及如何去做：</p> <ol><li><p>需要使用何种命令</p> <ul><li>首先，是要了解，对于读取数据，要用什么命令：根据前面关于 Nand Flash 的命令集合介绍，我们知道，要读取数据，要用到 Read 命令，该命令需要2个周期，第一个周期发 0x00，第二个周期发 0x30。</li></ul></li> <li><p>发送命令前的准备工作以及时序图各个信号的具体含义</p> <ul><li>知道了用何命令后，再去了解如何发送这些命令。</li></ul></li></ol> <blockquote><p><strong>提示</strong></p> <p>使能（Enable），是指使其（某个信号）有效，使其生效的意思，“使其”“能够”怎么怎么样。。。。比如，上面图中的CLE线号，是高电平有效，如果此时将其设为高电平，我们就叫做，将 CLE 使能，也就是使其生效的意思。</p></blockquote> <p><strong>图 2.23.2 Nand Flash 数据读取操作的时序图</strong></p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.7p6tknb4e.webp" loading="lazy" class="lazy"></p> <p>我们来一起看看，图6中的特意标注的①边上的黄色竖线。</p> <p>黄色竖线所处的时刻，是在发送读操作的第一个周期的命令 0x00 之前的那一刻。</p> <p>让我们看看，在那一刻，其所穿过好几行都对应什么值，以及进一步理解，为何要那个值。</p> <ol><li>黄色竖线穿过的第一行，是 CLE。将 CLE 置1，就说明你将要通过 I/O 复用端口发送进入 Nand Flash 的，是命令，而不是地址或者其他类型的数据。只有这样将 CLE 置1，使其有效，才能去通知了内部硬件逻辑，你接下来将收到的是命令，内部硬件逻辑，才会将受到的命令，放到命令寄存器中，才能实现后面正确的操作，否则，不去将 CLE 置1使其有效，硬件会无所适从，不知道你传入的到底是数据还是命令了。</li> <li>而第二行，是 CE#，那一刻的值是0。这个道理很简单，你既然要向 Nand Flash 发命令，那么先要选中它，所以，要保证 CE# 为低电平，使其有效，也就是片选有效。</li> <li>第三行是 WE#，意思是写使能。因为接下来是往 Nand Flash 里面写命令，所以，要使得 WE# 有效，所以设为低电平。</li> <li>第四行，是 ALE 是低电平，而 ALE 是高电平有效，此时意思就是使其无效。而对应地，前面介绍的，使 CLE 有效，因为将要数据的是命令（此时是发送图示所示的读命令第二周期的 0x30），而不是地址。如果在其他某些场合，比如接下来的要输入地址的时候，就要使其有效，而使 CLE 无效了。</li> <li>第五行，RE#，此时是高电平，无效。可以看到，知道后面低6阶段，才变成低电平，才有效，因为那时候，要发生读取命令，去读取数据。</li> <li>第六行，就是我们重点要介绍的，复用的输入输出 I/O 端口了，此刻，还没有输入数据，接下来，在不同的阶段，会输入或输出不同的数据/地址。</li> <li>第七行，R/B#，高电平，表示 R（Ready）/就绪，因为到了后面的第5阶段，硬件内部，在第四阶段，接受了外界的读取命令后，把该页的数据一点点送到页寄存器中，这段时间，属于系统在忙着干活，属于忙的阶段，所以，R/B# 才变成低，表示 Busy 忙的状态的。</li></ol> <p>介绍了时刻①的各个信号的值，以及为何是这个值之后，相信，后面的各个时刻，对应的不同信号的各个值，大家就会自己慢慢分析了，也就容易理解具体的操作顺序和原理了。</p> <h3 id="_2-24-如何计算出我们要传入的行地址和列地址"><a href="#_2-24-如何计算出我们要传入的行地址和列地址" class="header-anchor">#</a> 2.24 如何计算出我们要传入的行地址和列地址</h3> <p>在介绍具体读取数据的详细流程之前，还要做一件事，那就是，先要搞懂我们要访问的地址，以及这些地址，如何分解后，一点点传入进去，使得硬件能识别才行。</p> <p>此处还是以 K9K8G08U0A 为例，此 Nand Flash，一共有8192个块，每个块内有64页，每个页是2K+64 Bytes。</p> <p>假设，我们要访问其中的第 7000 个块中的第64页中的 1208 字节处的地址，此时，我们就要先把具体的地址算出来：</p> <p>物理地址 = 块大小×块号 ＋ 页大小×页号 ＋ 页内地址</p> <p>= 128K×7000 ＋ 2K×64 ＋ 1208</p> <p>= 0x36B204B8</p> <p>接下来，我们就看看，怎么才能把这个实际的物理地址，转化为 Nand Flash 所要求的格式。</p> <p>在解释地址组成之前，先要来看看其 datasheet 中关于地址周期的介绍：</p> <p><strong>图 2.24 Nand Flash的地址周期组成</strong></p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.1aow4goxp0.webp" loading="lazy" class="lazy"></p> <p>结合图 2.23.2“Nand Flash数据读取操作的时序图”中的 2，3阶段，我们可以看出，此 Nand Flash 地址周期共有5个，2个列(Column)周期，3个行（Row）周期。</p> <ol><li><p>对应地，列地址 A0~A10，就是页内地址，地址范围是从0到2047。</p> <p>细心的读者可能注意到了，为何此处多出来个A11呢？</p> <p>这样从 A0 到 A11，一共就是12位，可以表示的范围就是0 ~ 2^12^，即 0 ~ 4096了。</p> <p>实际上，由于我们访问页内地址，可能会访问到 oob 的位置，即 2048-2111 这 64 个字节的范围内，所以，此处实际上只用到了 2048～2111，用于表示页内的 oob 区域，其大小是64字节。</p></li> <li><p>对应地，A12～A30，称作页号，页的号码，可以定位到具体是哪一个页。</p> <p>A18～A30，表示对应的块号，即属于哪个块。</p></li></ol> <p>简单解释完了地址组成，那么就很容易分析上面例子中的地址了。</p> <p><strong>==注意，下面这样的方法，是错误的：==</strong></p> <p>0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000，分别分配到5个地址周期就是：</p> <table><thead><tr><th>1^st^周期</th> <th>A7 ～ A0</th> <th>1011 1000 = 0xB8</th></tr></thead> <tbody><tr><td>2^nd^周期</td> <td>A11～ A8</td> <td><em>0</em>100 = 0x04</td></tr> <tr><td>3^rd^周期</td> <td>A19～A12</td> <td>0010 0000 = 0x20</td></tr> <tr><td>4^th^周期</td> <td>A27～A20</td> <td>0110 1011 = 0x6B</td></tr> <tr><td>5^th^周期</td> <td>A30～A28</td> <td>11 = 0x03</td></tr></tbody></table> <blockquote><p>注意：</p> <p>与图 2.23.2 “Nand Flash 数据读取操作的时序图”中对应的，*L，意思是地电平，由于未用到那些位，datasheet 中强制要求设为0，所以，才有上面的 2^nd^ 周期中的高4位是 0000.其他的 A30 之后的位也是类似原理，都是0。</p></blockquote> <p>而至于上述计算方法为何是错误的，那是因为上面计算过程中，把第11位的值，本来是属于页号的位A11，给算成页内地址里面的值了。</p> <p>应该是这样计算，才是对的：</p> <p>0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000</p> <table><thead><tr><th>1^st^周期</th> <th>A7 ～ A0</th> <th>1011 1000 = 0xB8</th></tr></thead> <tbody><tr><td>2^nd^周期</td> <td>A10～ A8</td> <td>100 = 0x04</td></tr> <tr><td>3^rd^周期</td> <td>A19～A12</td> <td>010 0000 0 = 0x40</td></tr> <tr><td>4^th^周期</td> <td>A27～A20</td> <td>110 1011 0 = 0xD6</td></tr> <tr><td>5^th^周期</td> <td>A30～A28</td> <td>11 0 = 0x06</td></tr></tbody></table> <p>那有人会问了，上面表11中，不是明明写的 A0 到 A30，其中包括 A11，不是正好对应着此处地址中的 bit0 到 bit30吗？</p> <p>实际上，表11中的 A11，是比较特殊的，只有当我们访问页内地址处于 oob 的位置，即属于2048 ~ 2111的时候，A11才会其效果，才会用 A0-A11 用来表示对应的某个属于 2048 ~ 2111 的某个值，属于 oob 的某个位置。</p> <p>而我们此处的页内地址为 1208，还没有超过 2047呢，所以A11肯定是0。</p> <p>这么解释，显得很绕，很难看懂。</p> <p>换种方式来解释，就容易听懂了：</p> <p>说白了，我们就是要访问第7000个块中的第64页中的1208字节处，对应着</p> <p>页内地址 = 1208 = 0x4B8</p> <ul><li>页号 = 块数×页数/块 + 块内的页号
<ul><li>= 7000×（128K/2K） + 64</li> <li>= 7000×64 + 64</li> <li>= 448064</li> <li>= 0x6D640</li></ul></li></ul> <p>也就是，我们要访问 0x6D640 页内的 0x4B8 地址，这样很好理解吧,^_^。</p> <p>然后对应的：</p> <p>页内地址 = 0x4B8</p> <p>分成两个对应的列地址，就变成</p> <p>0x4B8 ：列地址1 = 0xB8，列地址2 = 0x04</p> <ul><li><p>页号 = 0x6D640，分成三个行号就是：</p> <ul><li>0x6D640：行号1 = 0x40，行号2 = 0xD6，行号3 = 0x06</li></ul></li> <li><p>再回头看看上面的计算方法，</p></li> <li><p>最开始计算出来的：</p> <ul><li>列地址1 = 0xB8</li> <li>列地址2 = 0x04</li> <li>行号1 = 0x20</li> <li>行号2 = 0x6B</li> <li>行号3 = 0x03</li></ul></li> <li><p>是错误的。</p></li> <li><p>而第二次计算正确的：</p> <ul><li>列地址1 = 0xB8</li> <li>列地址2 = 0x04</li> <li>行号1 = 0x40</li> <li>行号2 = 0xD6</li> <li>行号3 = 0x06</li></ul></li> <li><p>才是对的，也和我们此处自己手动计算，是一致的。</p></li> <li><p>第一次之所以计算错，就是错误的把行地址的最低一位 A11，放到列地址中的最高位了。</p></li> <li><p>至此，才算把如何手动计算行地址和列地址，解释明白和正确了。</p></li> <li><p>因此，我们要访问第7000个块中的第64页中的1208字节处的话，所要传入的地址就是分5个周期：</p></li> <li><p>分别传入两个列地址的：</p> <ul><li>列地址1 = 0xB8</li> <li>列地址2 = 0x04</li></ul></li> <li><p>然后再传3个行地址的：</p> <ul><li>行号1 = 0x40</li> <li>行号2 = 0xD6</li> <li>行号3 = 0x06</li></ul></li> <li><p>这样硬件才能识别。</p></li> <li><p>而接下来的内容，也就是介绍硬件是如何处理这些输入的。</p></li></ul> <h3 id="_2-25-读操作过程的解释"><a href="#_2-25-读操作过程的解释" class="header-anchor">#</a> 2.25 读操作过程的解释</h3> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.7p6tknb4e.webp" loading="lazy" class="lazy"></p> <p>准备工作终于完了，下面就可以开始解释说明，对于读操作的，上面图中标出来的，1-6个阶段，具体是什么含义。</p> <p>操作准备阶段：此处是读（Read）操作，所以，先发一个读命令的第一个阶段的 0x00,表示，让硬件先准备一下，接下来的操作是读。</p> <p>发送两个周期的列地址。也就是页内地址，表示，我要从一个页的什么位置开始读取数据。</p> <p>接下来再传入三个行地址。对应的也就是页号。</p> <p>然后再发一个读操作的第二个周期的命令0x30。接下来，就是硬件内部自己的事情了。</p> <p>Nand Flash 内部硬件逻辑，负责去按照你的要求，根据传入的地址，找到哪个块中的哪个页，然后把整个这一页的数据，都一点点搬运到页缓存中去。而在此期间，你所能做的事，也就只需要去读取状态寄存器，看看对应的位的值，也就是 R/B# 那一位，是1还是0，0的话，就表示，系统是 busy，仍在”忙“（着读取数据），如果是1，就说系统活干完了，忙清了，已经把整个页的数据都搬运到页缓存里去了，你可以接下来读取你要的数据了。</p> <p>对于这里。估计有人会问了，这一个页一共 2048+64 字节，如果我传入的页内地址，就像上面给的1208一类的值，只是想读取 1208 到 2011 这部分数据，而不是页开始的0地址整个页的数据，那么内部硬件却读取整个页的数据出来，岂不是很浪费吗？答案是，的确很浪费，效率看起来不高，但是实际就是这么做的，而且本身读取整个页的数据，相对时间并不长，而且读出来之后，内部数据指针会定位到你刚才所制定的 1208 的那个位置。</p> <p>接下来，就是你“窃取“系统忙了半天之后的劳动成果的时候了，呵呵。通过先去 Nand Flash 的控制器中的数据寄存器中写入你要读取多少个字节(byte)/字(word)，然后就可以去 Nand Flash 的控制器的 FIFO 中，一点点读取你要的数据了。</p> <p>至此，整个 Nand Flash 的读操作就完成了。</p> <p>对于其他操作，可以根据我上面的分析，一点点自己去看 datasheet，根据里面的时序图去分析具体的操作过程，然后对照代码，会更加清楚具体是如何实现的。</p> <h2 id="_3-nand-flash-的一些高级特性"><a href="#_3-nand-flash-的一些高级特性" class="header-anchor">#</a> 3. Nand Flash 的一些高级特性</h2> <h3 id="_3-1-nand-flash-的-unique-id"><a href="#_3-1-nand-flash-的-unique-id" class="header-anchor">#</a> 3.1 Nand Flash 的 Unique ID</h3> <p>Unique ID，翻译为中文就是，独一无二的ID，唯一性标识。</p> <p>很明显，这个 Unique ID 是为了用来识别某些东西的，每一个东西都拥有一个独一无二的标识信息。</p> <p>在 Nand Flash 里面的 Unique ID，主要是某个ID信息，保证每个 Nand Flash 都是独一无二的。主要用于其它的使用 Nand Flash 的用户，根据此 unique id 去做加密等应用，实现某些安全方面的应用。</p> <p>简而言之，就是用 Nand Flash 的 Unique ID 来实现安全相关的应用，比如加密，版权保护等等。</p> <h3 id="_3-2-片选无关-ce-don-t-care-技术"><a href="#_3-2-片选无关-ce-don-t-care-技术" class="header-anchor">#</a> 3.2 片选无关(CE don’t-care)技术</h3> <p>很多 Nand flash 支持一个叫做 CE don’t-care 的技术，字面意思就是，不关心是否片选。</p> <p>对此也许有人会问了，如果不片选，那还能对其操作吗？答案就是，这个技术，主要用在当时是不需要选中芯片，但是芯片内部却仍可以继续操作的这些情况：在某些应用，比如录音，音频播放等应用中，外部使用的微秒（us）级的时钟周期，此处假设是比较少的2us，在进行读取一页或者对页编程时，是对 Nand Flash 操作，这样的串行（Serial Access）访问的周期都是 20/30/50ns，都是纳秒（ns）级的，此处假设是50ns，当你已经发了对应的读或写的命令之后，接下来只是需要 Nand Flash 内部去自己操作，将数据读取除了或写入进去到内部的数据寄存器中而已，此处，如果可以把片选取消，CE# 是低电平有效，取消片选就是拉高电平，这样会在下一个外部命令发送过来之前，即微秒量级的时间里面，即2us－50ns≈2us，这段时间的取消片选，可以降低很少的系统功耗，但是多次的操作，就可以在很大程度上降低整体的功耗了。</p> <p>总的来说就是：由于某些外部应用所需要的访问 Nand Flash 的频率比较低，而 Nand Flash 内部操作速度比较快，所以在针对 Nand Flash 的读或写操作的大部分时间里面，都是在等待外部命令的输入，同时却选中芯片，产生了多余的功耗，此“不关心片选”技术，就是在 Nand Flash 的内部的相对快速的操作（读或写）完成之后，就取消片选，以节省系统功耗。待下次外部命令/数据/地址输入来的时候，再选中芯片，即可正常继续操作了。这样，整体上，就可以大大降低系统功耗了。</p> <blockquote><p><strong>提示</strong></p> <ol><li>如果想要操作硬件 Nand Flash 芯片，先要将对应的 CE#（低有效）片选信号拉低，选中该芯片，然后才能做接下来的读写操作所要做的发命令，发数据等动作。</li> <li>Nand Flash 的片选与否，功耗差别会有很大。如果数据没有记错的话，我之前遇到我们系统里面的 Nand Flash 的片选，大概有5个mA 的电流输出呢，也许你对 5mA 没太多概念，给你说个数据你就知道了：当时为了针对 MP3 播放功耗进行优化，整个系统优化之后的待机功耗，也才10个 mA 左右的，所以节省 5mA 已经算是很不错的功耗优化了。</li></ol></blockquote> <h3 id="_3-3-带-edc-的拷回操作以及-sector-的定义-copy-back-operation-with-edc-sector-definition-for-edc"><a href="#_3-3-带-edc-的拷回操作以及-sector-的定义-copy-back-operation-with-edc-sector-definition-for-edc" class="header-anchor">#</a> 3.3 带 EDC 的拷回操作以及 Sector 的定义（Copy-Back Operation with EDC &amp; Sector Definition for EDC）</h3> <p>Copy-Back 功能，简单的说就是，将一个页的数据，拷贝到另一个页。</p> <p>如果没有 Copy-Back 功能，那么正常的做法就是，先要将那个页的数据拷贝出来放到内存的数据 buffer 中，读出来之后，再用写命令将这页的数据，写到新的页里面。</p> <p>而 Copy-Back 功能的好处在于，不需要用到外部的存储空间，不需要读出来放到外部的 buffer 里面，而是可以直接读取数据到内部的页寄存器（page register）然后写到新的页里面去。</p> <p>而且，为了保证数据的正确，要硬件支持 EDC（Error Detection Code）的，否则，在数据的拷贝过程中，可能会出现错误，并且拷贝次数多了，可能会累积更多错误。</p> <p>而对于错误检测来说，硬件一般支持的是512字节数据，对应有16字节用来存放校验产生的 ECC 数值，而这512字节一般叫做一个扇区。对于 2K＋64 字节大小的页来说，按照512字节分，分别叫做 A，B，C，D 区，而后面的64字节的 oob 区域，按照16字节一个区，分别叫做 E，F，G，H 区，对应存放 A，B，C，D 数据区的 ECC 的值。</p> <p>Copy-Back 编程的主要作用在于，去掉了数据串行读取出来，再串行写入进去的时间，所以，而这部分操作，是比较耗时的，所以此技术可以提高编程效率，提高系统整体性能。</p> <h3 id="_3-4-多片同时编程-simultaneously-program-multi-plane"><a href="#_3-4-多片同时编程-simultaneously-program-multi-plane" class="header-anchor">#</a> 3.4 多片同时编程(Simultaneously Program Multi Plane)</h3> <p>对于有些新出的 Nand Flash，支持同时对多个片进行编程，比如上面提到的三星的 K9K8G08U0A，内部包含4片(Plane)，分别叫做 Plane0，Plane1，Plane2，Plane3。由于硬件上，对于每一个 Plane，都有对应的大小是2048+64 = 2112 字节的页寄存器（Page Register），使得同时支持多个 Plane 编程成为可能。 K9K8G08U0A 支持同时对2个 Plane 进行编程。</p> <p>不过要注意的是，只能对 Plane0 和 Plane1 或者 Plane2 和 Plane3，同时编程，而不支持 Plane0 和 Plane2 同时编程。</p> <h3 id="_3-5-交错页编程-interleave-page-program"><a href="#_3-5-交错页编程-interleave-page-program" class="header-anchor">#</a> 3.5 交错页编程（Interleave Page Program）</h3> <p>多片同时编程，是针对一个 chip 里面的多个 Plane 来说的，</p> <p>而此处的交错页编程，是指对多个 chip 而言的。</p> <p>可以先对一个 chip，假设叫 chip1，里面的一页进行编程，然后此时，chip1 内部就开始将数据一点点写到页里面，就出于忙的状态了，而此时可以利用这个时间，对出于就绪状态的 chip2，也进行页编程，发送对应的命令后，chip2 内部也就开始慢慢的写数据到存储单元里面去了，也出于忙的状态了。此时，再去检查 chip1，如果编程完成了，就可以开始下一页的编程了，然后发完命令后，就让其内部慢慢的编程吧，再去检查 chip2，如果也是编程完了，也就可以进行接下来的其他页的编程了。如此，交互操作 chip1 和 chip2，就可以有效地利用时间，使得整体编程效率提高近2倍，大大提高 Nand Flash 的编程/擦写速度了。</p> <h3 id="_3-6-随机输出页内数据-random-data-output-in-a-page"><a href="#_3-6-随机输出页内数据-random-data-output-in-a-page" class="header-anchor">#</a> 3.6 随机输出页内数据（Random Data Output In a Page）</h3> <p>在介绍此特性之前，先要说说，与 Random Data Output In a Page 相对应的是，普通的，正常的，sequential data output in a page。</p> <p>正常情况下，我们读取数据，都是先发读命令，然后等待数据从存储单元到内部的页数据寄存器中后，我们通过不断地将RE# (Read Enale，低电平有效)置低，然后从我们开始传入的列的起始地址，一点点读出我们要的数据，直到页的末尾，当然有可能还没到页地址的末尾，就不再读了。所谓的顺序（sequential）读取也就是，根据你之前发送的列地址的起始地址开始，每读一个字节的数据出来，内部的数据指针就加1，移到下个字节的地址，然后你再读下一个字节数据，就可以读出来你要的数据了，直到读取全部的数据出来为止。</p> <p>而此处的随机（random）读取，就是在你正常的顺序读取的过程中，先发一个随机读取的开始命令 0x05 命令，再传入你要将内部那个数据指针定位到具体什么地址，也就是2个 cycle 的列地址，然后再发随机读取结束命令 0xE0，然后，内部那个数据地址指针，就会移动到你所制定的位置了，你接下来再读取的数据，就是从那个制定地址开始的数据了。</p> <p>而 Nand Flash 数据手册里面也说了，这样的随机读取，你可以多次操作，没限制的。</p> <p>请注意，上面你所传入的地址，都是列地址，也就是页内地址，也就是说，对于页大小为 2K 的 Nand Flash 来说，所传入的地址，应该是小于 2048+64＝2112 的。</p> <p>不过，实际在 Nand Flash 的使用中，好像这种用法很少的。绝大多数，都是顺序读取数据。</p> <h2 id="_4-nand-flash-相关规范-onfi-和-lba"><a href="#_4-nand-flash-相关规范-onfi-和-lba" class="header-anchor">#</a> 4. Nand Flash 相关规范 -- ONFI 和 LBA</h2> <h3 id="_4-1-onfi-是什么"><a href="#_4-1-onfi-是什么" class="header-anchor">#</a> 4.1 ONFI 是什么</h3> <p>ONFI 规范，即 Open Nand Flash Interface specification。</p> <p>ONFI 是 Intel 主导的，其他一些厂家（Hynix，Micron，Numonyx，Phison ，SanDisk，Sony，Spansion等）参与制定的，统一了 Nand Flash 的操作接口。</p> <p>所谓操作接口，就是那些对 Nand Flash 操作的命令等内容。</p> <p>而所谓统一，意思是之前那些 Nand Flash 的操作命令等，都是各自为政，虽然大多数常见的 Nand Flash 的操作，比如 page read 的命令是 0x00,0x30，page write 的命令是 0x80,0x10 等，但是有些命令相关的内容，很特别且很重要的一个例子就是，每个厂家的 Nand Flash 的 read id 的命令，虽然都是 0x90，但是读取出来的几个字节的含义，每个厂家定义的都不太一样。</p> <p>因此，才有统一 Nand Flash 的操作接口这一说。</p> <p>ONFI规范官网：http://onfi.org</p> <p><strong>图 4.1 ONFI中的Nand Flash的命令集合</strong></p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.2yy91p9doc.webp" loading="lazy" class="lazy"></p> <p>可以看到，其中常见的一些命令，比如</p> <ol><li>page read（0x00,0x30）</li> <li>page write（0x80,0x10）</li> <li>block erase（0x60,0xD0）</li> <li>Reset（0xFF）</li></ol> <p>等等命令，都是和普通的 Nand Flash 的命令是一样的，而额外多出一些命令，比如 Read Unique ID（0xED）等命令，是之前某些 Nand Flash 命令所不具有的。</p> <p>如此，定义了 Nand Flash 的操作的命令的集合以及发送对应命令所遵循的时序等内容。</p> <h3 id="_4-2-onfi-block-abstracted-nand"><a href="#_4-2-onfi-block-abstracted-nand" class="header-anchor">#</a> 4.2 ONFI Block Abstracted NAND</h3> <p>ONFI 还定义了另外一个规范：</p> <p>ONFI Block Abstracted Nand Specification</p> <p>即 ONFI LBA Nand，简单说就是，逻辑块寻址的 Nand。其含义和 Toshiba 的 LBA，基本没有太多区别。</p> <h3 id="_4-3-onfi-的好处"><a href="#_4-3-onfi-的好处" class="header-anchor">#</a> 4.3 ONFI 的好处</h3> <p>ONFI 规范定义了之后，每家厂商的 Nand Flash，只要符合这个 ONFI 规范，然后上层 Nand Flash 的软件，就可以统一只用一种了，换句话说，我写了一份 Nand Flash 的驱动后，就可以操作所有和 ONFI 兼容的 Nand Flash 了，整个 Nand Flash 的兼容性，上层软件的兼容性，互操作性，就大大提高了。</p> <p>而且，同样的，由于任何规范在定义的时候，都会考虑到兼容性和扩展性，ONFI 也不例外。针对符合 ONFI 规范的，写好的软件，除了可以操作多家与 ONFI 兼容的 Nand Flash 之外，而对于以后出现的新的技术，新制程的 Nand Flash，只要符合 ONFI 规范，也同样可以支持，可以在旧的软件下工作，而不需要由于 Nand Flash 的更新换代，而更改上层软件和驱动，这个优势，由于对于将 Nand Flash 芯片集成到自己系统中的相关开发人员来说，是个好消息。</p> <h3 id="_4-4-lba-规范是什么"><a href="#_4-4-lba-规范是什么" class="header-anchor">#</a> 4.4 LBA 规范是什么</h3> <p>LBA Nand Flash，Logical Block Address，逻辑块寻址的 Nand Flash，是 Nand Flash 大厂之一的 Toshiba，自己独立设计出来的新一代的 Nand Flash 的规范。</p> <p>之所以叫做逻辑块寻址，是相对于之前常见的，普通的 Nand Flash 的物理块的寻址来说的。常见的 Nand Flash，如果要读取和写入数据，所用的对应的地址是对应的：block地址+block 内的 Page 地址+Page 内的偏移量 = 绝对的物理地址，</p> <p>此物理块寻址，相对来说有个缺点，那就是，由于之前提到的 Nand Flash 会出现使用过程中出现坏块，所以，遇到这样的坏块，首先坏块管理要去将此坏块标记，然后将坏块的数据拷贝到另一个好的 block 中，再继续访问新的 block。</p> <p>而且数据读写过程中，还要有对应的 ECC 校验，很多情况下，也都是软件来实现这部分的工作，即使是硬件的 ECC 校验，也要写少量的软件，去操作对应寄存器，读取 ECC 校验的结果，当然别忘了，还有对应的负载平衡等工作。</p> <p>如此的这类的坏块管理工作，对于软件来说，很是繁重，而且整个系统实现起来也不是很容易，所以，才催生了一个想法，是否可以把 ECC 校验，负载平衡，坏块管理，全部都放到硬件实现上，而对于软件来说，我都不关心，只关心有多少个 Block 供我使用，用于数据读写。</p> <p>针对于此需求，Toshiba 推出了 LBA 逻辑块寻址的 Nand Flash，在 Nand Flash 存储芯片之外，加了对应一个硬件控制权 Controller，实现了上述的坏块管理，ECC 校验，负载平衡等工作，这样使得人家想要用你 LBA 的 Nand Flash 的人，去开发对应的软件来驱动 LBA Nand Flash 工作，相对要做的事情，就少了很多，相对来说就是减轻了软件系统集成方面的工作，提高了开发效率，缩短了产品上市周期。</p> <p>关于 Toshiba LBA Nand 规范，在此多说一点（参考附录中：lba-core.c）：</p> <p>LBA Nand 分为 PNP，VFP 和 MDP 三种分区：</p> <ol><li>PNP 主要用于存放 Uboot 等启动代码</li> <li>VFP 主要用于存放 uImage 等内核代码</li> <li>MDP 主要用于存放用户的数据，以及 rootfs 等内容</li></ol> <h3 id="_4-5-onfi-和-lba-的区别和联系"><a href="#_4-5-onfi-和-lba-的区别和联系" class="header-anchor">#</a> 4.5 ONFI 和 LBA 的区别和联系</h3> <p>总的来说，ONFI在对于旧的Nand Flash的兼容上，都是相对类似的。</p> <ul><li>ONFI 规范，更注重对于 Nand Flash 的操作接口方面的定义 + ONFI LBA Nand 的定义</li> <li>而 Toshiba LBA 规范，主要侧重于LBA的Nand的定义。</li></ul> <p>Toshiba 的 LBA 规范中，又多了些其他模式和应用类别。</p> <p>总的来说，可以这么划分：</p> <ul><li>ONFI = Nand Flash 操作接口的统一 + ONFI 的 LBA Nand</li> <li>Toshiba LBA = 等价于 ONFI 的 LBA Nand + 多种模式和对应的不同应用</li></ul> <h2 id="_5-内存技术设备-mtd-memory-technology-device"><a href="#_5-内存技术设备-mtd-memory-technology-device" class="header-anchor">#</a> 5. 内存技术设备 MTD（Memory Technology Device）</h2> <p>MTD，是 Linux 的存储设备中的一个子系统。其设计此系统的目的是，对于内存类的设备，提供一个抽象层，一个接口，使得对于硬件驱动设计者来说，可以尽量少的去关心存储格式，比如 FTL，FFS2 等，而只需要去提供最简单的底层硬件设备的读/写/擦除函数就可以了。而对于数据对于上层使用者来说是如何表示的，硬件驱动设计者可以不关心，而 MTD 存储设备子系统都帮你做好了。</p> <p>对于 MTD 子系统的好处，简单解释就是，他帮助你实现了，很多对于以前或者其他系统来说，本来也是你驱动设计者要去实现的很多功能。换句话说，有了 MTD，使得你设计 Nand Flash 的驱动，所要做的事情，要少很多很多，因为大部分工作，都由 MTD 帮你做好了。</p> <p>当然，这个好处的一个“副作用”就是，使得我们不了解的人去理解整个 Linux 驱动架构，以及 MTD，变得更加复杂。但是，总的说，觉得是利远远大于弊，否则，就不仅需要你理解，而且还是做更多的工作，实现更多的功能了。</p> <p>此外，还有一个重要的原因，那就是，前面提到的 Nand Flash 和普通硬盘等设备的特殊性：</p> <p>有限的通过出复用来实现输入输出命令和地址/数据等的 IO 接口，最小单位是页而不是常见的 bit，写前需擦除等，导致了这类设备，不能像平常对待硬盘等操作一样去操作，只能采取一些特殊方法，这就诞生了 MTD 设备的统一抽象层。</p> <p>MTD，将 Nand Flash，nor flash 和其他类型的 flash 等设备，统一抽象成 MTD 设备来管理，根据这些设备的特点，上层实现了常见的操作函数封装，底层具体的内部实现，就需要驱动设计者自己来实现了。具体的内部硬件设备的读/写/擦除函数，那就是你必须实现的了。</p> <p><strong>表 5.1 MTD设备和硬盘设备之间的区别</strong></p> <table><thead><tr><th>HARD drives</th> <th>MTD device</th></tr></thead> <tbody><tr><td>连续的扇区</td> <td>连续的可擦除块</td></tr> <tr><td>扇区都很小(512B,1024B)</td> <td>可擦除块比较大 (32KB,128KB)</td></tr> <tr><td>主要通过两个操作对其维护操作：读扇区，写扇区</td> <td>主要通过三个操作对其维护操作：从擦除块中读，写入擦除块，<em>擦写可擦除块</em></td></tr> <tr><td>坏快被重新映射，并且被硬件隐藏起来了（至少是在如今常见的LBA硬盘设备中是如此）</td> <td>坏的可擦除块没有被隐藏，软件中要处理对应的坏块问题</td></tr> <tr><td>HDD 扇区没有擦写寿命超出的问题</td> <td>可擦除块是有擦除次数限制的，大概是 10^4^ - 10^5^ 次</td></tr></tbody></table> <h3 id="_5-1-读操作的硬件到软件的映射"><a href="#_5-1-读操作的硬件到软件的映射" class="header-anchor">#</a> 5.1 读操作的硬件到软件的映射</h3> <p>下面这部分主要介绍一下，关于硬件的设计和规范，是如何映射到具体的软件实现的，看了这部分内容之后，你对如何根据硬件的规范去用软件代码实现对应的功能，就有了大概的了解了，然后去实现对应的某硬件的驱动，就有了大概的脉络了。</p> <p>关于硬件部分的细节，前面其实已经介绍过了，但是为了方便说明，此处还是以读操作为例去讲解硬件到软件是如何映射的。</p> <p>再次贴出上面的那个图：</p> <p><strong>Nand Flash数据读取操作的时序图</strong></p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.7p6tknb4e.webp" loading="lazy" class="lazy"></p> <p>对于上面的从1到6，每个阶段所表示的含义，再简单解释一下：</p> <ol><li>此阶段，是读命令第一个周期，发送的命令为 0x00。</li> <li>此阶段，依次发送列地址，关于这些行地址，列地址等是如何计算出来的，后面的内容会有详细解释。</li> <li>此阶段是发送对应的行地址</li> <li>此阶段是发送读命令第二周期 2nd cycle 所对应的命令，0x30</li> <li>此阶段是等待时间，等待Nand Flash硬件上准备好对应的数据，以便后续读出。</li> <li>此阶段，就是一点点地把所需要的数据读出来。</li></ol></div></div> <!----> <div class="page-edit"><!----> <!----> <!----></div> <div class="page-nav-wapper"><div class="page-nav-centre-wrap"><a href="/pages/1eb351/" class="page-nav-centre page-nav-centre-prev"><div class="tooltip">【详解】Nand Flash 必看知识 1</div></a> <!----></div> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/pages/1eb351/" class="prev">【详解】Nand Flash 必看知识 1</a></span> <!----></p></div></div></div> <!----></main></div> <div class="footer"><!----> 
  Theme by
  <a href="https://github.com/xugaoyi/vuepress-theme-vdoing" target="_blank" title="本站主题">Vdoing</a> 
    | Copyright © 2023-2025
    <span>霜晨月</span></div> <div class="buttons"><div title="返回顶部" class="button blur go-to-top iconfont icon-fanhuidingbu" style="display:none;"></div> <div title="去评论" class="button blur go-to-comment iconfont icon-pinglun" style="display:none;"></div> <div title="主题模式" class="button blur theme-mode-but iconfont icon-zhuti"><ul class="select-box" style="display:none;"><li class="iconfont icon-zidong">
          跟随系统
        </li><li class="iconfont icon-rijianmoshi">
          浅色模式
        </li><li class="iconfont icon-yejianmoshi">
          深色模式
        </li><li class="iconfont icon-yuedu">
          阅读模式
        </li></ul></div></div> <!----> <!----> <!----></div><div class="global-ui"><canvas id="vuepress-canvas-cursor"></canvas></div></div>
    <script src="/assets/js/app.67adcfd9.js" defer></script><script src="/assets/js/4.9aaa1650.js" defer></script><script src="/assets/js/1.5474518c.js" defer></script><script src="/assets/js/3.593d14fc.js" defer></script><script src="/assets/js/190.c284595f.js" defer></script>
  </body>
</html>
